lib/http/2/framer.rb in http-2-0.11.0 vs lib/http/2/framer.rb in http-2-0.12.0
- old
+ new
@@ -1,9 +1,10 @@
+# frozen_string_literal: true
+
module HTTP2
# Performs encoding, decoding, and validation of binary HTTP/2 frames.
#
- # rubocop:disable ClassLength
class Framer
include Error
# Default value of max frame size (16384 bytes)
DEFAULT_MAX_FRAME_SIZE = 2**14
@@ -17,86 +18,86 @@
# Maximum window increment value (2^31)
MAX_WINDOWINC = 0x7fffffff
# HTTP/2 frame type mapping as defined by the spec
FRAME_TYPES = {
- data: 0x0,
- headers: 0x1,
- priority: 0x2,
- rst_stream: 0x3,
- settings: 0x4,
- push_promise: 0x5,
- ping: 0x6,
- goaway: 0x7,
+ data: 0x0,
+ headers: 0x1,
+ priority: 0x2,
+ rst_stream: 0x3,
+ settings: 0x4,
+ push_promise: 0x5,
+ ping: 0x6,
+ goaway: 0x7,
window_update: 0x8,
- continuation: 0x9,
- altsvc: 0xa,
+ continuation: 0x9,
+ altsvc: 0xa
}.freeze
- FRAME_TYPES_WITH_PADDING = [:data, :headers, :push_promise].freeze
+ FRAME_TYPES_WITH_PADDING = %i[data headers push_promise].freeze
# Per frame flags as defined by the spec
FRAME_FLAGS = {
data: {
- end_stream: 0,
+ end_stream: 0,
padded: 3,
- compressed: 5,
+ compressed: 5
},
headers: {
- end_stream: 0,
+ end_stream: 0,
end_headers: 2,
padded: 3,
- priority: 5,
+ priority: 5
},
- priority: {},
- rst_stream: {},
- settings: { ack: 0 },
+ priority: {},
+ rst_stream: {},
+ settings: { ack: 0 },
push_promise: {
end_headers: 2,
- padded: 3,
+ padded: 3
},
- ping: { ack: 0 },
- goaway: {},
+ ping: { ack: 0 },
+ goaway: {},
window_update: {},
continuation: { end_headers: 2 },
- altsvc: {},
+ altsvc: {}
}.each_value(&:freeze).freeze
# Default settings as defined by the spec
DEFINED_SETTINGS = {
- settings_header_table_size: 1,
- settings_enable_push: 2,
+ 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,
+ 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,
- internal_error: 2,
+ no_error: 0,
+ protocol_error: 1,
+ internal_error: 2,
flow_control_error: 3,
- settings_timeout: 4,
- stream_closed: 5,
- frame_size_error: 6,
- refused_stream: 7,
- cancel: 8,
- compression_error: 9,
- connect_error: 10,
- enhance_your_calm: 11,
+ settings_timeout: 4,
+ stream_closed: 5,
+ frame_size_error: 6,
+ refused_stream: 7,
+ cancel: 8,
+ compression_error: 9,
+ connect_error: 10,
+ enhance_your_calm: 11,
inadequate_security: 12,
- http_1_1_required: 13,
+ http_1_1_required: 13
}.freeze
RBIT = 0x7fffffff
RBYTE = 0x0fffffff
EBIT = 0x80000000
- UINT32 = 'N'.freeze
- UINT16 = 'n'.freeze
- UINT8 = 'C'.freeze
+ UINT32 = 'N'
+ UINT16 = 'n'
+ UINT8 = 'C'
HEADERPACK = (UINT8 + UINT16 + UINT8 + UINT8 + UINT32).freeze
FRAME_LENGTH_HISHIFT = 16
FRAME_LENGTH_LOMASK = 0xFFFF
private_constant :RBIT, :RBYTE, :EBIT, :HEADERPACK, :UINT32, :UINT16, :UINT8
@@ -113,38 +114,28 @@
# @param frame [Hash]
# @return [String]
def common_header(frame)
header = []
- unless FRAME_TYPES[frame[:type]]
- fail CompressionError, "Invalid frame type (#{frame[:type]})"
- end
+ raise CompressionError, "Invalid frame type (#{frame[:type]})" unless FRAME_TYPES[frame[:type]]
- if frame[:length] > @max_frame_size
- fail CompressionError, "Frame size is too large: #{frame[:length]}"
- end
+ raise CompressionError, "Frame size is too large: #{frame[:length]}" if frame[:length] > @max_frame_size
- if frame[:length] < 0
- fail CompressionError, "Frame size is invalid: #{frame[:length]}"
- end
+ raise CompressionError, "Frame size is invalid: #{frame[:length]}" if (frame[:length]).negative?
- if frame[:stream] > MAX_STREAM_ID
- fail CompressionError, "Stream ID (#{frame[:stream]}) is too large"
- end
+ raise CompressionError, "Stream ID (#{frame[:stream]}) is too large" if frame[:stream] > MAX_STREAM_ID
if frame[:type] == :window_update && frame[:increment] > MAX_WINDOWINC
- fail CompressionError, "Window increment (#{frame[:increment]}) is too large"
+ raise 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]
- unless position
- fail CompressionError, "Invalid frame flag (#{f}) for #{frame[:type]}"
- end
+ raise CompressionError, "Invalid frame flag (#{f}) for #{frame[:type]}" unless position
acc | (1 << position)
end
header << frame[:stream]
@@ -157,14 +148,14 @@
def read_common_header(buf)
frame = {}
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.find { |_t, pos| type == pos }
+ frame[:type], = FRAME_TYPES.find { |_t, pos| type == pos }
if frame[:type]
frame[:flags] = FRAME_FLAGS[frame[:type]].each_with_object([]) do |(name, pos), acc|
- acc << name if (flags & (1 << pos)) > 0
+ acc << name if (flags & (1 << pos)).positive?
end
end
frame[:stream] = stream & RBIT
frame
@@ -187,12 +178,13 @@
length += frame[:payload].bytesize
when :headers
if frame[:weight] || frame[:stream_dependency] || !frame[:exclusive].nil?
unless frame[:weight] && frame[:stream_dependency] && !frame[:exclusive].nil?
- fail CompressionError, "Must specify all of priority parameters for #{frame[:type]}"
+ raise CompressionError, "Must specify all of priority parameters for #{frame[:type]}"
end
+
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)
@@ -203,32 +195,31 @@
bytes << frame[:payload]
length += frame[:payload].bytesize
when :priority
unless frame[:weight] && frame[:stream_dependency] && !frame[:exclusive].nil?
- fail CompressionError, "Must specify all of priority parameters for #{frame[:type]}"
+ raise CompressionError, "Must specify all of priority parameters for #{frame[:type]}"
end
+
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])
length += 4
when :settings
- if (frame[:stream]).nonzero?
- fail CompressionError, "Invalid stream ID (#{frame[:stream]})"
- end
+ raise CompressionError, "Invalid stream ID (#{frame[:stream]})" if (frame[:stream]).nonzero?
frame[:payload].each do |(k, v)|
if k.is_a? Integer
DEFINED_SETTINGS.value?(k) || next
else
k = DEFINED_SETTINGS[k]
- fail CompressionError, "Unknown settings ID for #{k}" if k.nil?
+ raise CompressionError, "Unknown settings ID for #{k}" if k.nil?
end
bytes << [k].pack(UINT16)
bytes << [v].pack(UINT32)
length += 6
@@ -239,12 +230,13 @@
bytes << frame[:payload]
length += 4 + frame[:payload].bytesize
when :ping
if frame[:payload].bytesize != 8
- fail CompressionError, "Invalid payload size (#{frame[:payload].size} != 8 bytes)"
+ raise CompressionError, "Invalid payload size (#{frame[:payload].size} != 8 bytes)"
end
+
bytes << frame[:payload]
length += 8
when :goaway
bytes << [frame[:last_stream] & RBIT].pack(UINT32)
@@ -266,20 +258,22 @@
when :altsvc
bytes << [frame[:max_age], frame[:port]].pack(UINT32 + UINT16)
length += 6
if frame[:proto]
- fail CompressionError, 'Proto too long' if frame[:proto].bytesize > 255
+ raise CompressionError, 'Proto too long' if frame[:proto].bytesize > 255
+
bytes << [frame[:proto].bytesize].pack(UINT8)
bytes << frame[:proto].force_encoding(Encoding::BINARY)
length += 1 + frame[:proto].bytesize
else
bytes << [0].pack(UINT8)
length += 1
end
if frame[:host]
- fail CompressionError, 'Host too long' if frame[:host].bytesize > 255
+ raise CompressionError, 'Host too long' if frame[:host].bytesize > 255
+
bytes << [frame[:host].bytesize].pack(UINT8)
bytes << frame[:host].force_encoding(Encoding::BINARY)
length += 1 + frame[:host].bytesize
else
bytes << [0].pack(UINT8)
@@ -294,27 +288,27 @@
# Process padding.
# frame[:padding] gives number of extra octets to be added.
# - http://tools.ietf.org/html/draft-ietf-httpbis-http2-16#section-6.1
if frame[:padding]
unless FRAME_TYPES_WITH_PADDING.include?(frame[:type])
- fail CompressionError, "Invalid padding flag for #{frame[:type]}"
+ raise CompressionError, "Invalid padding flag for #{frame[:type]}"
end
padlen = frame[:padding]
if padlen <= 0 || padlen > 256 || padlen + length > @max_frame_size
- fail CompressionError, "Invalid padding #{padlen}"
+ raise CompressionError, "Invalid padding #{padlen}"
end
length += padlen
bytes.prepend([padlen -= 1].pack(UINT8))
frame[:flags] << :padded
# Padding: Padding octets that contain no application semantic value.
# Padding octets MUST be set to zero when sending and ignored when
# receiving.
- bytes << "\0" * padlen
+ bytes << ("\0" * padlen)
end
frame[:length] = length
bytes.prepend(common_header(frame))
end
@@ -323,14 +317,15 @@
# does not contain enough data, no further work is performed.
#
# @param buf [Buffer]
def parse(buf)
return nil if buf.size < 9
+
frame = read_common_header(buf)
return nil if buf.size < 9 + frame[:length]
- fail ProtocolError, 'payload too large' if frame[:length] > DEFAULT_MAX_FRAME_SIZE
+ raise ProtocolError, 'payload too large' if frame[:length] > DEFAULT_MAX_FRAME_SIZE
buf.read(9)
payload = buf.read(frame[:length])
# Implementations MUST discard frames
@@ -341,14 +336,15 @@
# 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
+ padlen = payload.read(1).unpack1(UINT8)
frame[:padding] = padlen + 1
- fail ProtocolError, 'padding too long' if padlen > payload.bytesize
- payload.slice!(-padlen, padlen) if padlen > 0
+ raise ProtocolError, 'padding too long' if padlen > payload.bytesize
+
+ payload.slice!(-padlen, padlen) if padlen.positive?
frame[:length] -= frame[:padding]
frame[:flags].delete(:padded)
end
end
@@ -373,25 +369,21 @@
when :settings
# NOTE: frame[:length] might not match the number of frame[:payload]
# because unknown extensions are ignored.
frame[:payload] = []
- unless (frame[:length] % 6).zero?
- fail ProtocolError, 'Invalid settings payload length'
- end
+ raise ProtocolError, 'Invalid settings payload length' unless (frame[:length] % 6).zero?
- if (frame[:stream]).nonzero?
- fail ProtocolError, "Invalid stream ID (#{frame[:stream]})"
- end
+ raise ProtocolError, "Invalid stream ID (#{frame[:stream]})" if (frame[:stream]).nonzero?
(frame[:length] / 6).times do
- id = payload.read(2).unpack(UINT16).first
+ id = payload.read(2).unpack1(UINT16)
val = payload.read_uint32
# Unsupported or unrecognized settings MUST be ignored.
# Here we send it along.
- name, _ = DEFINED_SETTINGS.find { |_name, v| v == id }
+ 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])
@@ -400,47 +392,44 @@
when :goaway
frame[:last_stream] = payload.read_uint32 & RBIT
frame[:error] = unpack_error payload.read_uint32
size = frame[:length] - 8 # for last_stream and error
- frame[:payload] = payload.read(size) if size > 0
+ frame[:payload] = payload.read(size) if size.positive?
when :window_update
frame[:increment] = payload.read_uint32 & RBIT
when :continuation
frame[:payload] = payload.read(frame[:length])
when :altsvc
frame[:max_age], frame[:port] = payload.read(6).unpack(UINT32 + UINT16)
len = payload.getbyte
- frame[:proto] = payload.read(len) if len > 0
+ frame[:proto] = payload.read(len) if len.positive?
len = payload.getbyte
- frame[:host] = payload.read(len) if len > 0
+ frame[:host] = payload.read(len) if len.positive?
- frame[:origin] = payload.read(payload.size) if payload.size > 0
+ frame[:origin] = payload.read(payload.size) unless payload.empty?
# else # Unknown frame type is explicitly allowed
end
frame
end
private
def pack_error(e)
unless e.is_a? Integer
- if DEFINED_ERRORS[e].nil?
- fail CompressionError, "Unknown error ID for #{e}"
- end
+ raise CompressionError, "Unknown error ID for #{e}" if DEFINED_ERRORS[e].nil?
e = DEFINED_ERRORS[e]
end
[e].pack(UINT32)
end
def unpack_error(e)
- name, _ = DEFINED_ERRORS.find { |_name, v| v == e }
+ name, = DEFINED_ERRORS.find { |_name, v| v == e }
name || error
end
end
- # rubocop:enable ClassLength
end