File: window_update_frame.rb

package info (click to toggle)
ruby-protocol-http2 0.24.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 484 kB
  • sloc: ruby: 3,671; makefile: 4
file content (260 lines) | stat: -rw-r--r-- 7,923 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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# frozen_string_literal: true

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

require "protocol/http2/window_update_frame"
require "protocol/http2/connection_context"
require "protocol/http2/a_frame"

describe Protocol::HTTP2::WindowUpdateFrame do
	let(:window_size_increment) {1024}
	let(:frame) {subject.new}
	
	it_behaves_like Protocol::HTTP2::AFrame do
		def before
			frame.pack window_size_increment
			
			super
		end
	end
	
	with "#pack" do
		it "packs data" do
			frame.pack window_size_increment
			
			expect(frame.length).to be == 4
		end
	end
	
	with "#unpack" do
		it "unpacks data" do
			frame.pack window_size_increment
			
			expect(frame.unpack).to be == window_size_increment
		end
	end
	
	with "#read_payload" do
		let(:stream) {StringIO.new([0, 0, 0, 0].pack("C*"))}
		
		with "a length other than 4" do
			it "raises an error" do
				frame.stream_id = 0
				frame.length = 2
				
				expect{frame.read_payload(stream)}.to raise_exception(
					Protocol::HTTP2::FrameSizeError,
					message: be =~ /Invalid frame length/
				)
			end
		end
	end
	
	with "a connection" do
		include_context Protocol::HTTP2::ConnectionContext
		
		let(:framer) {client.framer}
		
		let(:settings) do
			[[Protocol::HTTP2::Settings::INITIAL_WINDOW_SIZE, 200]]
		end
		
		let(:headers) do
			[[":method", "GET"], [":authority", "Earth"]]
		end
		
		let(:stream) do
			client.create_stream
		end
		
		def before
			client.send_connection_preface do
				server.read_connection_preface(settings)
			end
			
			client.read_frame until client.state == :open
			server.read_frame until server.state == :open
			
			stream.send_headers(headers)
			expect(server.read_frame).to be_a Protocol::HTTP2::HeadersFrame
			
			super
		end
		
		it "can determine available frame size" do
			expect(client.available_frame_size).to be == 16384
			expect(server.available_frame_size).to be == 16384
			
			# Fake the maximum frame size:
			maximum_frame_size = server.available_size + 1
			expect(server.available_frame_size(maximum_frame_size)).to be == server.available_size
			
			expect(stream.maximum_frame_size).to be == 16384
		end
		
		it "can consume data frames" do
			frame = Protocol::HTTP2::DataFrame.new
			frame.length = client.available_size
			
			client.consume_remote_window(frame)
			expect(client.available_size).to be == 0
		end
		
		it "fails if it tries to consume more than the available window" do
			frame = Protocol::HTTP2::DataFrame.new
			frame.length = client.available_size + 1
			
			expect do
				client.consume_remote_window(frame)
			end.to raise_exception(Protocol::HTTP2::FlowControlError, message: be =~ /exceeded window size/)
		end
		
		it "should assign capacity according to settings" do
			expect(client.remote_settings.initial_window_size).to be == 200
			expect(server.local_settings.initial_window_size).to be == 200
			
			expect(client.remote_window.capacity).to be == 0xFFFF
			expect(server.local_window.capacity).to be == 0xFFFF
			
			expect(client.local_window.capacity).to be == 0xFFFF
			expect(server.remote_window.capacity).to be == 0xFFFF
			
			expect(client.local_settings.initial_window_size).to be == 0xFFFF
			expect(server.remote_settings.initial_window_size).to be == 0xFFFF
		end
		
		it "should send window update after exhausting half of the available window" do
			# Write 60 bytes of data.
			stream.send_data("*" * 60)
			
			expect(stream.remote_window.used).to be == 60
			expect(client.remote_window.used).to be == 60
			
			# puts "Server #{server} #{server.remote_window.inspect} reading frame..."
			expect(server.read_frame).to be_a Protocol::HTTP2::DataFrame
			expect(server.local_window.used).to be == 60
			
			# Write another 60 bytes which passes the 50% threshold.
			stream.send_data("*" * 60)
			
			# The server receives a data frame...
			expect(server.read_frame).to be_a Protocol::HTTP2::DataFrame
			
			# ...and must respond with a window update:
			frame = client.read_frame
			expect(frame).to be_a Protocol::HTTP2::WindowUpdateFrame
			
			expect(frame.unpack).to be == 120
		end
		
		with "#expand" do
			it "should expand the window" do
				expect(client.remote_window.used).to be == 0
				expect(client.remote_window.capacity).to be == 0xFFFF
				expect(client.remote_window).not.to be(:full?)
				
				client.remote_window.expand(100)
				
				expect(client.remote_window.used).to be == -100
				expect(client.remote_window.capacity).to be == 0xFFFF
			end
			
			it "should not expand the window beyond the maximum" do
				expect(client.remote_window.used).to be == 0
				expect(client.remote_window.capacity).to be == 0xFFFF
				
				expect do
					client.remote_window.expand(Protocol::HTTP2::MAXIMUM_ALLOWED_WINDOW_SIZE + 1)
				end.to raise_exception(Protocol::HTTP2::FlowControlError)
				
				expect(client.remote_window.used).to be == 0
				expect(client.remote_window.capacity).to be == 0xFFFF
			end
		end
		
		with "#receive_window_update" do
			it "should be invoked when window update is received" do
				# Write 200 bytes of data (client -> server) which exhausts server local window
				stream.send_data("*" * 200)
				
				expect(server.read_frame).to be_a Protocol::HTTP2::DataFrame
				expect(server.local_window.used).to be == 200
				expect(client.remote_window.used).to be == 200
				
				# Window update was sent, and used data was zeroed:
				server_stream = server.streams[stream.id]
				expect(server_stream.local_window.used).to be == 0
				
				# ...and must respond with a window update for the stream:
				expect(stream).to receive(:receive_window_update).once
				frame = client.read_frame
				expect(frame).to be_a Protocol::HTTP2::WindowUpdateFrame
				expect(frame.unpack).to be == 200
			end
			
			it "should be invoked when window update is received for the connection" do
				frame = nil
				
				(client.available_size / 200).times do
					stream.send_data("*" * 200)
					expect(server.read_frame).to be_a Protocol::HTTP2::DataFrame
					frame = client.read_frame
					expect(frame).to be_a Protocol::HTTP2::WindowUpdateFrame
				end
				
				expect(client).to receive(:receive_window_update).twice
				
				stream.send_data("*" * client.available_size)
				expect(server.read_frame).to be_a Protocol::HTTP2::DataFrame
				
				frame = client.read_frame
				expect(frame).to be_a(Protocol::HTTP2::WindowUpdateFrame)
				expect(frame).to be(:connection?) # stream_id = 0
				
				frame = client.read_frame
				expect(frame).to be_a Protocol::HTTP2::WindowUpdateFrame
				expect(frame).to have_attributes(stream_id: be == stream.id)
				
				stream.send_data("*" * 200)
				expect(server.read_frame).to be_a Protocol::HTTP2::DataFrame
			end
			
			it "should send stream reset if window update is invalid" do
				window_update_frame = Protocol::HTTP2::WindowUpdateFrame.new(stream.id)
				window_update_frame.pack(0)
				
				server.write_frame(window_update_frame)
				
				expect(stream).to receive(:send_reset_stream)
				client.read_frame
			end
			
			it "should fail when window update is received for an idle stream" do
				window_update_frame = Protocol::HTTP2::WindowUpdateFrame.new(stream.id+2)
				window_update_frame.pack(100)
				
				server.write_frame(window_update_frame)
				
				expect do
					frame = client.read_frame
				end.to raise_exception(Protocol::HTTP2::ProtocolError, message: be =~ /Cannot update window of idle stream/)
			end
		end
		
		with "desired capacity" do
			it "should send window updates only as needed" do
				expect(client.local_window.desired).to be == 0xFFFF
				
				server_stream = server[stream.id]
				
				# Send a data frame that will consume less than half of the desired capacity:
				server_stream.send_data("*" * 0xFF)
				
				expect(client.read_frame).to be_a Protocol::HTTP2::DataFrame
				expect(client.local_window.used).to be == 0xFF
				expect(client.local_window).not.to be(:limited?)
			end
		end
	end
end