File: continuation_frame.rb

package info (click to toggle)
ruby-protocol-http2 0.23.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 472 kB
  • sloc: ruby: 3,627; makefile: 4
file content (146 lines) | stat: -rw-r--r-- 4,724 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2019-2025, by Samuel Williams.

require_relative "frame"

module Protocol
	module HTTP2
		# Module for frames that can be continued with CONTINUATION frames.
		module Continued
			# @constant [Integer] The maximum number of continuation frames to read to prevent resource exhaustion.
			LIMIT = 8
			
			# Initialize a continuable frame.
			# @parameter arguments [Array] Arguments passed to parent constructor.
			def initialize(*)
				super
				
				@continuation = nil
			end
			
			# Check if this frame has continuation frames.
			# @returns [Boolean] True if there are continuation frames.
			def continued?
				!!@continuation
			end
			
			# Check if this is the last header block fragment.
			# @returns [Boolean] True if the END_HEADERS flag is set.
			def end_headers?
				flag_set?(END_HEADERS)
			end
			
			# Read the frame and any continuation frames from the stream.
			#
			# There is an upper limit to the number of continuation frames that can be read to prevent resource exhaustion. If the limit is 0, only one frame will be read (the initial frame). Otherwise, the limit decrements with each continuation frame read.
			#
			# @parameter stream [IO] The stream to read from.
			# @parameter maximum_frame_size [Integer] Maximum allowed frame size.
			# @parameter limit [Integer] The maximum number of continuation frames to read.
			def read(stream, maximum_frame_size, limit = LIMIT)
				super(stream, maximum_frame_size)
				
				unless end_headers?
					if limit.zero?
						raise ProtocolError, "Too many continuation frames!"
					end
					
					continuation = ContinuationFrame.new
					continuation.read_header(stream)
					
					# We validate the frame type here:
					unless continuation.valid_type?
						raise ProtocolError, "Invalid frame type: #{@type}!"
					end
					
					if continuation.stream_id != @stream_id
						raise ProtocolError, "Invalid stream id: #{continuation.stream_id} for continuation of stream id: #{@stream_id}!"
					end
					
					continuation.read(stream, maximum_frame_size, limit - 1)
					
					@continuation = continuation
				end
			end
			
			# Write the frame and any continuation frames to the stream.
			# @parameter stream [IO] The stream to write to.
			def write(stream)
				super
				
				if continuation = self.continuation
					continuation.write(stream)
				end
			end
			
			attr_accessor :continuation
			
			# Pack data into this frame, creating continuation frames if needed.
			# @parameter data [String] The data to pack.
			# @parameter options [Hash] Options including maximum_size.
			def pack(data, **options)
				maximum_size = options[:maximum_size]
				
				if maximum_size and data.bytesize > maximum_size
					clear_flags(END_HEADERS)
					
					super(data.byteslice(0, maximum_size), **options)
					
					remainder = data.byteslice(maximum_size, data.bytesize-maximum_size)
					
					@continuation = ContinuationFrame.new
					@continuation.pack(remainder, maximum_size: maximum_size)
				else
					set_flags(END_HEADERS)
					
					super data, **options
					
					@continuation = nil
				end
			end
			
			# Unpack data from this frame and any continuation frames.
			# @returns [String] The complete unpacked data.
			def unpack
				if @continuation.nil?
					super
				else
					super + @continuation.unpack
				end
			end
		end
		
		# The CONTINUATION frame is used to continue a sequence of header block fragments. Any number of CONTINUATION frames can be sent, as long as the preceding frame is on the same stream and is a HEADERS, PUSH_PROMISE, or CONTINUATION frame without the END_HEADERS flag set.
		#
		# +---------------------------------------------------------------+
		# |                   Header Block Fragment (*)                 ...
		# +---------------------------------------------------------------+
		#
		class ContinuationFrame < Frame
			include Continued
			
			TYPE = 0x9
			
			# Read the frame and any continuation frames from the stream.
			# @parameter stream [IO] The stream to read from.
			# @parameter maximum_frame_size [Integer] Maximum allowed frame size.
			# @parameter limit [Integer] The maximum number of continuation frames to read.
			def read(stream, maximum_frame_size, limit = 8)
				super
			end
			
			# This is only invoked if the continuation is received out of the normal flow.
			def apply(connection)
				connection.receive_continuation(self)
			end
			
			# Get a string representation of the continuation frame.
			# @returns [String] Human-readable frame information.
			def inspect
				"\#<#{self.class} stream_id=#{@stream_id} flags=#{@flags} length=#{@length || 0}b>"
			end
		end
	end
end