File: window.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 (141 lines) | stat: -rw-r--r-- 4,878 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
# frozen_string_literal: true

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

module Protocol
	module HTTP2
		# Flow control window for managing HTTP/2 data flow.
		class Window
			# When an HTTP/2 connection is first established, new streams are created with an initial flow-control window size of 65,535 octets. The connection flow-control window is also 65,535 octets.
			DEFAULT_CAPACITY = 0xFFFF
			
			# Initialize a new flow control window.
			# @parameter capacity [Integer] The initial window size, typically from the settings.
			def initialize(capacity = DEFAULT_CAPACITY)
				# This is the main field required:
				@available = capacity
				
				# These two fields are primarily used for efficiently sending window updates:
				@used = 0
				@capacity = capacity
			end
			
			# The window is completely full?
			def full?
				@available <= 0
			end
			
			attr :used
			attr :capacity
			
			# When the value of SETTINGS_INITIAL_WINDOW_SIZE changes, a receiver MUST adjust the size of all stream flow-control windows that it maintains by the difference between the new value and the old value.
			def capacity= value
				difference = value - @capacity
				
				# An endpoint MUST treat a change to SETTINGS_INITIAL_WINDOW_SIZE that causes any flow-control window to exceed the maximum size as a connection error of type FLOW_CONTROL_ERROR.
				if (@available + difference) > MAXIMUM_ALLOWED_WINDOW_SIZE
					raise FlowControlError, "Changing window size by #{difference} caused overflow: #{@available + difference} > #{MAXIMUM_ALLOWED_WINDOW_SIZE}!"
				end
				
				@available += difference
				@capacity = value
			end
			
			# Consume a specific amount from the available window.
			# @parameter amount [Integer] The amount to consume from the window.
			def consume(amount)
				@available -= amount
				@used += amount
			end
			
			attr :available
			
			# Check if there is available window capacity.
			# @returns [Boolean] True if there is available capacity.
			def available?
				@available > 0
			end
			
			# Expand the window by a specific amount.
			# @parameter amount [Integer] The amount to expand the window by.
			# @raises [FlowControlError] If expansion would cause overflow.
			def expand(amount)
				available = @available + amount
				
				if available > MAXIMUM_ALLOWED_WINDOW_SIZE
					raise FlowControlError, "Expanding window by #{amount} caused overflow: #{available} > #{MAXIMUM_ALLOWED_WINDOW_SIZE}!"
				end
				
				# puts "expand(#{amount}) @available=#{@available}"
				@available += amount
				@used -= amount
			end
			
			# Get the amount of window that should be reclaimed.
			# @returns [Integer] The amount of used window space.
			def wanted
				@used
			end
			
			# Check if the window is limited and needs updating.
			# @returns [Boolean] True if available capacity is less than half of total capacity.
			def limited?
				@available < (@capacity / 2)
			end
			
			# Get a string representation of the window.
			# @returns [String] Human-readable window information.
			def inspect
				"\#<#{self.class} available=#{@available} used=#{@used} capacity=#{@capacity}#{limited? ? " limited" : nil}>"
			end
			
			alias to_s inspect
		end
		
		# This is a window which efficiently maintains a desired capacity.
		class LocalWindow < Window
			# Initialize a local window with optional desired capacity.
			# @parameter capacity [Integer] The initial window capacity.
			# @parameter desired [Integer] The desired window capacity.
			def initialize(capacity = DEFAULT_CAPACITY, desired: nil)
				super(capacity)
				
				# The desired capacity of the window, may be bigger than the initial capacity.
				# If that is the case, we will likely send a window update to the remote end to increase the capacity.
				@desired = desired
			end
			
			# The desired capacity of the window.
			attr_accessor :desired
			
			# Get the amount of window that should be reclaimed, considering desired capacity.
			# @returns [Integer] The amount needed to reach desired capacity or used space.
			def wanted
				if @desired
					# We must send an update which allows at least @desired bytes to be sent.
					(@desired - @capacity) + @used
				else
					super
				end
			end
			
			# Check if the window is limited, considering desired capacity.
			# @returns [Boolean] True if window needs updating based on desired capacity.
			def limited?
				if @desired
					# Do not send window updates until we are less than half the desired capacity:
					@available < (@desired / 2)
				else
					super
				end
			end
			
			# Get a string representation of the local window.
			# @returns [String] Human-readable local window information.
			def inspect
				"\#<#{self.class} available=#{@available} used=#{@used} capacity=#{@capacity} desired=#{@desired} #{limited? ? "limited" : nil}>"
			end
		end
	end
end