| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Protocol::HTTP2::Connection; |
|
2
|
12
|
|
|
12
|
|
3926
|
use strict; |
|
|
12
|
|
|
|
|
19
|
|
|
|
12
|
|
|
|
|
268
|
|
|
3
|
12
|
|
|
12
|
|
33
|
use warnings; |
|
|
12
|
|
|
|
|
10
|
|
|
|
12
|
|
|
|
|
266
|
|
|
4
|
|
|
|
|
|
|
use Protocol::HTTP2::Constants |
|
5
|
12
|
|
|
|
|
3642
|
qw(const_name :frame_types :errors :settings :flags :states |
|
6
|
12
|
|
|
12
|
|
1935
|
:limits :endpoints); |
|
|
12
|
|
|
|
|
16
|
|
|
7
|
12
|
|
|
12
|
|
4232
|
use Protocol::HTTP2::HeaderCompression qw(headers_encode); |
|
|
12
|
|
|
|
|
20
|
|
|
|
12
|
|
|
|
|
643
|
|
|
8
|
12
|
|
|
12
|
|
4255
|
use Protocol::HTTP2::Frame; |
|
|
12
|
|
|
|
|
24
|
|
|
|
12
|
|
|
|
|
316
|
|
|
9
|
12
|
|
|
12
|
|
4471
|
use Protocol::HTTP2::Stream; |
|
|
12
|
|
|
|
|
22
|
|
|
|
12
|
|
|
|
|
314
|
|
|
10
|
12
|
|
|
12
|
|
4194
|
use Protocol::HTTP2::Upgrade; |
|
|
12
|
|
|
|
|
19
|
|
|
|
12
|
|
|
|
|
305
|
|
|
11
|
12
|
|
|
12
|
|
58
|
use Protocol::HTTP2::Trace qw(tracer); |
|
|
12
|
|
|
|
|
10
|
|
|
|
12
|
|
|
|
|
23193
|
|
|
12
|
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
# Mixin |
|
14
|
|
|
|
|
|
|
our @ISA = |
|
15
|
|
|
|
|
|
|
qw(Protocol::HTTP2::Frame Protocol::HTTP2::Stream Protocol::HTTP2::Upgrade); |
|
16
|
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
# Default settings |
|
18
|
|
|
|
|
|
|
my %default_settings = ( |
|
19
|
|
|
|
|
|
|
&SETTINGS_HEADER_TABLE_SIZE => DEFAULT_HEADER_TABLE_SIZE, |
|
20
|
|
|
|
|
|
|
&SETTINGS_ENABLE_PUSH => DEFAULT_ENABLE_PUSH, |
|
21
|
|
|
|
|
|
|
&SETTINGS_MAX_CONCURRENT_STREAMS => DEFAULT_MAX_CONCURRENT_STREAMS, |
|
22
|
|
|
|
|
|
|
&SETTINGS_INITIAL_WINDOW_SIZE => DEFAULT_INITIAL_WINDOW_SIZE, |
|
23
|
|
|
|
|
|
|
&SETTINGS_MAX_FRAME_SIZE => DEFAULT_MAX_FRAME_SIZE, |
|
24
|
|
|
|
|
|
|
&SETTINGS_MAX_HEADER_LIST_SIZE => DEFAULT_MAX_HEADER_LIST_SIZE, |
|
25
|
|
|
|
|
|
|
); |
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
sub new { |
|
28
|
45
|
|
|
45
|
0
|
12263
|
my ( $class, $type, %opts ) = @_; |
|
29
|
45
|
100
|
|
|
|
4325
|
my $self = bless { |
|
30
|
|
|
|
|
|
|
type => $type, |
|
31
|
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
streams => {}, |
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
last_stream => $type == CLIENT ? 1 : 2, |
|
35
|
|
|
|
|
|
|
last_peer_stream => 0, |
|
36
|
|
|
|
|
|
|
active_peer_streams => 0, |
|
37
|
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
encode_ctx => { |
|
39
|
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
# HPACK. Header Table |
|
41
|
|
|
|
|
|
|
header_table => [], |
|
42
|
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
# HPACK. Header Table size |
|
44
|
|
|
|
|
|
|
ht_size => 0, |
|
45
|
|
|
|
|
|
|
max_ht_size => DEFAULT_HEADER_TABLE_SIZE, |
|
46
|
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
settings => {%default_settings}, |
|
48
|
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
}, |
|
50
|
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
decode_ctx => { |
|
52
|
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
# HPACK. Header Table |
|
54
|
|
|
|
|
|
|
header_table => [], |
|
55
|
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
# HPACK. Header Table size |
|
57
|
|
|
|
|
|
|
ht_size => 0, |
|
58
|
|
|
|
|
|
|
max_ht_size => DEFAULT_HEADER_TABLE_SIZE, |
|
59
|
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
# HPACK. Emitted headers |
|
61
|
|
|
|
|
|
|
emitted_headers => [], |
|
62
|
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
# last frame |
|
64
|
|
|
|
|
|
|
frame => {}, |
|
65
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
settings => {%default_settings}, |
|
67
|
|
|
|
|
|
|
}, |
|
68
|
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
# Current error |
|
70
|
|
|
|
|
|
|
error => 0, |
|
71
|
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
# Output frames queue |
|
73
|
|
|
|
|
|
|
queue => [], |
|
74
|
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
# Connection must be shutdown |
|
76
|
|
|
|
|
|
|
shutdown => 0, |
|
77
|
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
# issued GOAWAY: no new streams on this connection |
|
79
|
|
|
|
|
|
|
goaway => 0, |
|
80
|
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
# get preface |
|
82
|
|
|
|
|
|
|
preface => 0, |
|
83
|
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
# perform upgrade |
|
85
|
|
|
|
|
|
|
upgrade => 0, |
|
86
|
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
# flow control |
|
88
|
|
|
|
|
|
|
fcw_send => DEFAULT_INITIAL_WINDOW_SIZE, |
|
89
|
|
|
|
|
|
|
fcw_recv => DEFAULT_INITIAL_WINDOW_SIZE, |
|
90
|
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
# stream where expected CONTINUATION frames |
|
92
|
|
|
|
|
|
|
pending_stream => undef, |
|
93
|
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
}, $class; |
|
95
|
|
|
|
|
|
|
|
|
96
|
45
|
|
|
|
|
3298
|
for (qw(on_change_state on_new_peer_stream on_error upgrade)) { |
|
97
|
180
|
100
|
|
|
|
6634
|
$self->{$_} = $opts{$_} if exists $opts{$_}; |
|
98
|
|
|
|
|
|
|
} |
|
99
|
|
|
|
|
|
|
|
|
100
|
45
|
100
|
|
|
|
3187
|
if ( exists $opts{settings} ) { |
|
101
|
19
|
|
|
|
|
1555
|
for ( keys %{ $opts{settings} } ) { |
|
|
19
|
|
|
|
|
3163
|
|
|
102
|
19
|
|
|
|
|
4720
|
$self->{decode_ctx}->{settings}->{$_} = $opts{settings}{$_}; |
|
103
|
|
|
|
|
|
|
} |
|
104
|
|
|
|
|
|
|
} |
|
105
|
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
# Sync decode context max_ht_size |
|
107
|
|
|
|
|
|
|
$self->{decode_ctx}->{max_ht_size} = |
|
108
|
45
|
|
|
|
|
3281
|
$self->{decode_ctx}->{settings}->{&SETTINGS_HEADER_TABLE_SIZE}; |
|
109
|
|
|
|
|
|
|
|
|
110
|
45
|
|
|
|
|
6384
|
$self; |
|
111
|
|
|
|
|
|
|
} |
|
112
|
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
sub decode_context { |
|
114
|
634
|
|
|
634
|
0
|
77231
|
shift->{decode_ctx}; |
|
115
|
|
|
|
|
|
|
} |
|
116
|
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
sub encode_context { |
|
118
|
74
|
|
|
74
|
0
|
6501
|
shift->{encode_ctx}; |
|
119
|
|
|
|
|
|
|
} |
|
120
|
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
sub pending_stream { |
|
122
|
207
|
|
|
207
|
0
|
28762
|
shift->{pending_stream}; |
|
123
|
|
|
|
|
|
|
} |
|
124
|
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
sub dequeue { |
|
126
|
407
|
|
|
407
|
0
|
26113
|
my $self = shift; |
|
127
|
407
|
|
|
|
|
25746
|
shift @{ $self->{queue} }; |
|
|
407
|
|
|
|
|
76610
|
|
|
128
|
|
|
|
|
|
|
} |
|
129
|
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
sub enqueue_raw { |
|
131
|
19
|
|
|
19
|
0
|
1598
|
my $self = shift; |
|
132
|
19
|
|
|
|
|
1581
|
push @{ $self->{queue} }, @_; |
|
|
19
|
|
|
|
|
4765
|
|
|
133
|
|
|
|
|
|
|
} |
|
134
|
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
sub enqueue { |
|
136
|
210
|
|
|
210
|
0
|
14387
|
my $self = shift; |
|
137
|
210
|
|
|
|
|
14750
|
while ( my ( $type, $flags, $stream_id, $data_ref ) = splice( @_, 0, 4 ) ) { |
|
138
|
212
|
|
|
|
|
14290
|
push @{ $self->{queue} }, |
|
|
212
|
|
|
|
|
28940
|
|
|
139
|
|
|
|
|
|
|
$self->frame_encode( $type, $flags, $stream_id, $data_ref ); |
|
140
|
212
|
|
|
|
|
14596
|
$self->state_machine( 'send', $type, $flags, $stream_id ); |
|
141
|
|
|
|
|
|
|
} |
|
142
|
|
|
|
|
|
|
} |
|
143
|
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
sub enqueue_first { |
|
145
|
2
|
|
|
2
|
0
|
321
|
my $self = shift; |
|
146
|
2
|
|
|
|
|
3
|
my $i = 0; |
|
147
|
2
|
|
|
|
|
2
|
for ( 0 .. $#{ $self->{queue} } ) { |
|
|
2
|
|
|
|
|
7
|
|
|
148
|
|
|
|
|
|
|
my $type = |
|
149
|
2
|
|
|
|
|
6
|
( $self->frame_header_decode( \$self->{queue}->[$_], 0 ) )[1]; |
|
150
|
2
|
100
|
66
|
|
|
9
|
last if $type != CONTINUATION && $type != PING; |
|
151
|
1
|
|
|
|
|
2
|
$i++; |
|
152
|
|
|
|
|
|
|
} |
|
153
|
2
|
|
|
|
|
8
|
while ( my ( $type, $flags, $stream_id, $data_ref ) = splice( @_, 0, 4 ) ) { |
|
154
|
2
|
|
|
|
|
1
|
splice @{ $self->{queue} }, $i++, 0, |
|
|
2
|
|
|
|
|
7
|
|
|
155
|
|
|
|
|
|
|
$self->frame_encode( $type, $flags, $stream_id, $data_ref ); |
|
156
|
2
|
|
|
|
|
10
|
$self->state_machine( 'send', $type, $flags, $stream_id ); |
|
157
|
|
|
|
|
|
|
} |
|
158
|
|
|
|
|
|
|
} |
|
159
|
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
sub finish { |
|
161
|
11
|
|
|
11
|
0
|
797
|
my $self = shift; |
|
162
|
|
|
|
|
|
|
$self->enqueue( GOAWAY, 0, 0, |
|
163
|
11
|
50
|
|
|
|
811
|
[ $self->{last_peer_stream}, $self->{error} ] ) |
|
164
|
|
|
|
|
|
|
unless $self->shutdown; |
|
165
|
11
|
|
|
|
|
824
|
$self->shutdown(1); |
|
166
|
|
|
|
|
|
|
} |
|
167
|
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
sub shutdown { |
|
169
|
30
|
|
|
30
|
0
|
1615
|
my $self = shift; |
|
170
|
30
|
100
|
|
|
|
1665
|
$self->{shutdown} = shift if @_; |
|
171
|
30
|
|
|
|
|
7255
|
$self->{shutdown}; |
|
172
|
|
|
|
|
|
|
} |
|
173
|
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
sub goaway { |
|
175
|
104
|
|
|
104
|
0
|
4831
|
my $self = shift; |
|
176
|
104
|
100
|
|
|
|
4892
|
$self->{goaway} = shift if @_; |
|
177
|
104
|
|
|
|
|
9675
|
$self->{goaway}; |
|
178
|
|
|
|
|
|
|
} |
|
179
|
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
sub preface { |
|
181
|
435
|
|
|
435
|
0
|
27587
|
my $self = shift; |
|
182
|
435
|
100
|
|
|
|
27430
|
$self->{preface} = shift if @_; |
|
183
|
435
|
|
|
|
|
57997
|
$self->{preface}; |
|
184
|
|
|
|
|
|
|
} |
|
185
|
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
sub upgrade { |
|
187
|
535
|
|
|
535
|
0
|
33656
|
my $self = shift; |
|
188
|
535
|
50
|
|
|
|
34265
|
$self->{upgrade} = shift if @_; |
|
189
|
535
|
|
|
|
|
68495
|
$self->{upgrade}; |
|
190
|
|
|
|
|
|
|
} |
|
191
|
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
sub state_machine { |
|
193
|
421
|
|
|
421
|
0
|
28976
|
my ( $self, $act, $type, $flags, $stream_id ) = @_; |
|
194
|
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
return |
|
196
|
421
|
50
|
66
|
|
|
61921
|
if $stream_id == 0 |
|
|
|
|
66
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
197
|
|
|
|
|
|
|
|| $type == SETTINGS |
|
198
|
|
|
|
|
|
|
|| $type == GOAWAY |
|
199
|
|
|
|
|
|
|
|| $self->upgrade |
|
200
|
|
|
|
|
|
|
|| !$self->preface; |
|
201
|
|
|
|
|
|
|
|
|
202
|
260
|
|
|
|
|
14913
|
my $promised_sid = $self->stream_promised_sid($stream_id); |
|
203
|
|
|
|
|
|
|
|
|
204
|
260
|
|
33
|
|
|
15099
|
my $prev_state = $self->{streams}->{ $promised_sid || $stream_id }->{state}; |
|
205
|
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
# REFUSED_STREAM error |
|
207
|
260
|
0
|
33
|
|
|
15043
|
return if !defined $prev_state && $type == RST_STREAM && $act eq 'send'; |
|
|
|
|
33
|
|
|
|
|
|
208
|
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
# Direction server->client |
|
210
|
|
|
|
|
|
|
my $srv2cln = ( $self->{type} == SERVER && $act eq 'send' ) |
|
211
|
260
|
|
66
|
|
|
15417
|
|| ( $self->{type} == CLIENT && $act eq 'recv' ); |
|
212
|
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
# Direction client->server |
|
214
|
|
|
|
|
|
|
my $cln2srv = ( $self->{type} == SERVER && $act eq 'recv' ) |
|
215
|
260
|
|
66
|
|
|
15402
|
|| ( $self->{type} == CLIENT && $act eq 'send' ); |
|
216
|
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
# Do we expect CONTINUATION after this frame? |
|
218
|
260
|
|
100
|
|
|
15126
|
my $pending = ( $type == HEADERS || $type == PUSH_PROMISE ) |
|
219
|
|
|
|
|
|
|
&& !( $flags & END_HEADERS ); |
|
220
|
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
#tracer->debug( |
|
222
|
|
|
|
|
|
|
# sprintf "\e[0;31mStream state: frame %s is %s%s on %s stream %i\e[m\n", |
|
223
|
|
|
|
|
|
|
# const_name( "frame_types", $type ), |
|
224
|
|
|
|
|
|
|
# $act, |
|
225
|
|
|
|
|
|
|
# $pending ? "*" : "", |
|
226
|
|
|
|
|
|
|
# const_name( "states", $prev_state ), |
|
227
|
|
|
|
|
|
|
# $promised_sid || $stream_id, |
|
228
|
|
|
|
|
|
|
#); |
|
229
|
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
# Wait until all CONTINUATION frames arrive |
|
231
|
260
|
100
|
|
|
|
14860
|
if ( my $ps = $self->stream_pending_state($stream_id) ) { |
|
|
|
50
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
232
|
1
|
50
|
|
|
|
5
|
if ( $type != CONTINUATION ) { |
|
|
|
50
|
|
|
|
|
|
|
233
|
0
|
|
|
|
|
0
|
tracer->error( |
|
234
|
|
|
|
|
|
|
sprintf "invalid frame type %s. Expected CONTINUATION frame\n", |
|
235
|
|
|
|
|
|
|
const_name( "frame_types", $type ) |
|
236
|
|
|
|
|
|
|
); |
|
237
|
0
|
|
|
|
|
0
|
$self->error(PROTOCOL_ERROR); |
|
238
|
|
|
|
|
|
|
} |
|
239
|
|
|
|
|
|
|
elsif ( $flags & END_HEADERS ) { |
|
240
|
1
|
50
|
|
|
|
2
|
$self->stream_promised_sid( $stream_id, undef ) if $promised_sid; |
|
241
|
1
|
|
33
|
|
|
6
|
$self->stream_pending_state( $promised_sid || $stream_id, undef ); |
|
242
|
1
|
|
33
|
|
|
6
|
$self->stream_state( $promised_sid || $stream_id, $ps ); |
|
243
|
|
|
|
|
|
|
} |
|
244
|
|
|
|
|
|
|
} |
|
245
|
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
# Unexpected CONTINUATION frame |
|
247
|
|
|
|
|
|
|
elsif ( $type == CONTINUATION ) { |
|
248
|
0
|
|
|
|
|
0
|
tracer->error("Unexpected CONTINUATION frame\n"); |
|
249
|
0
|
|
|
|
|
0
|
$self->error(PROTOCOL_ERROR); |
|
250
|
|
|
|
|
|
|
} |
|
251
|
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
# State machine |
|
253
|
|
|
|
|
|
|
# IDLE |
|
254
|
|
|
|
|
|
|
elsif ( $prev_state == IDLE ) { |
|
255
|
76
|
100
|
66
|
|
|
3392
|
if ( $type == HEADERS && $cln2srv ) { |
|
|
|
50
|
33
|
|
|
|
|
|
|
|
50
|
33
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
256
|
75
|
100
|
|
|
|
3355
|
$self->stream_state( $stream_id, |
|
257
|
|
|
|
|
|
|
( $flags & END_STREAM ) ? HALF_CLOSED : OPEN, $pending ); |
|
258
|
|
|
|
|
|
|
} |
|
259
|
|
|
|
|
|
|
elsif ( $type == PUSH_PROMISE && $srv2cln ) { |
|
260
|
0
|
|
|
|
|
0
|
$self->stream_state( $promised_sid, RESERVED, $pending ); |
|
261
|
0
|
0
|
|
|
|
0
|
$self->stream_promised_sid( $stream_id, undef ) |
|
262
|
|
|
|
|
|
|
if $flags & END_HEADERS; |
|
263
|
|
|
|
|
|
|
} |
|
264
|
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
# first frame in stream is invalid, so state is yet IDLE |
|
266
|
|
|
|
|
|
|
elsif ( $type == RST_STREAM && $act eq 'send' ) { |
|
267
|
0
|
|
|
|
|
0
|
tracer->notice('send RST_STREAM on IDLE state. possible bug?'); |
|
268
|
0
|
|
|
|
|
0
|
$self->stream_state( $stream_id, CLOSED ); |
|
269
|
|
|
|
|
|
|
} |
|
270
|
|
|
|
|
|
|
elsif ( $type != PRIORITY ) { |
|
271
|
0
|
|
|
|
|
0
|
tracer->error( |
|
272
|
|
|
|
|
|
|
sprintf "invalid frame type %s for current stream state %s\n", |
|
273
|
|
|
|
|
|
|
const_name( "frame_types", $type ), |
|
274
|
|
|
|
|
|
|
const_name( "states", $prev_state ) |
|
275
|
|
|
|
|
|
|
); |
|
276
|
0
|
|
|
|
|
0
|
$self->error(PROTOCOL_ERROR); |
|
277
|
|
|
|
|
|
|
} |
|
278
|
|
|
|
|
|
|
} |
|
279
|
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
# OPEN |
|
281
|
|
|
|
|
|
|
elsif ( $prev_state == OPEN ) { |
|
282
|
13
|
100
|
33
|
|
|
74
|
if ( ( $flags & END_STREAM ) |
|
|
|
50
|
66
|
|
|
|
|
|
|
|
50
|
33
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
283
|
|
|
|
|
|
|
&& ( $type == DATA || $type == HEADERS ) ) |
|
284
|
|
|
|
|
|
|
{ |
|
285
|
4
|
|
|
|
|
11
|
$self->stream_state( $stream_id, HALF_CLOSED, $pending ); |
|
286
|
|
|
|
|
|
|
} |
|
287
|
|
|
|
|
|
|
elsif ( $type == RST_STREAM ) { |
|
288
|
0
|
|
|
|
|
0
|
$self->stream_state( $stream_id, CLOSED ); |
|
289
|
|
|
|
|
|
|
} |
|
290
|
|
|
|
|
|
|
elsif ($type == HEADERS |
|
291
|
|
|
|
|
|
|
&& !$pending |
|
292
|
|
|
|
|
|
|
&& $self->stream_trailer($stream_id) ) |
|
293
|
|
|
|
|
|
|
{ |
|
294
|
0
|
|
|
|
|
0
|
tracer->error("expected END_STREAM flag for trailer HEADERS frame"); |
|
295
|
0
|
|
|
|
|
0
|
$self->error(PROTOCOL_ERROR); |
|
296
|
|
|
|
|
|
|
} |
|
297
|
|
|
|
|
|
|
} |
|
298
|
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
# RESERVED (local/remote) |
|
300
|
|
|
|
|
|
|
elsif ( $prev_state == RESERVED ) { |
|
301
|
0
|
0
|
0
|
|
|
0
|
if ( $type == RST_STREAM ) { |
|
|
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
302
|
0
|
|
|
|
|
0
|
$self->stream_state( $stream_id, CLOSED ); |
|
303
|
|
|
|
|
|
|
} |
|
304
|
|
|
|
|
|
|
elsif ( $type == HEADERS && $srv2cln ) { |
|
305
|
0
|
0
|
|
|
|
0
|
$self->stream_state( $stream_id, |
|
306
|
|
|
|
|
|
|
( $flags & END_STREAM ) ? CLOSED : HALF_CLOSED, $pending ); |
|
307
|
|
|
|
|
|
|
} |
|
308
|
|
|
|
|
|
|
elsif ( $type != PRIORITY && $cln2srv ) { |
|
309
|
0
|
|
|
|
|
0
|
tracer->error("invalid frame $type for state RESERVED"); |
|
310
|
0
|
|
|
|
|
0
|
$self->error(PROTOCOL_ERROR); |
|
311
|
|
|
|
|
|
|
} |
|
312
|
|
|
|
|
|
|
} |
|
313
|
|
|
|
|
|
|
|
|
314
|
|
|
|
|
|
|
# HALF_CLOSED (local/remote) |
|
315
|
|
|
|
|
|
|
elsif ( $prev_state == HALF_CLOSED ) { |
|
316
|
165
|
100
|
66
|
|
|
10906
|
if ( ( $type == RST_STREAM ) |
|
|
|
50
|
66
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
317
|
|
|
|
|
|
|
|| ( ( $flags & END_STREAM ) && $srv2cln ) ) |
|
318
|
|
|
|
|
|
|
{ |
|
319
|
70
|
|
|
|
|
3351
|
$self->stream_state( $stream_id, CLOSED, $pending ); |
|
320
|
|
|
|
|
|
|
} |
|
321
|
190
|
|
|
|
|
51686
|
elsif ( ( !grep { $type == $_ } ( WINDOW_UPDATE, PRIORITY ) ) |
|
322
|
|
|
|
|
|
|
&& $cln2srv ) |
|
323
|
|
|
|
|
|
|
{ |
|
324
|
0
|
|
|
|
|
0
|
tracer->error( sprintf "invalid frame %s for state HALF CLOSED\n", |
|
325
|
|
|
|
|
|
|
const_name( "frame_types", $type ) ); |
|
326
|
0
|
|
|
|
|
0
|
$self->error(PROTOCOL_ERROR); |
|
327
|
|
|
|
|
|
|
} |
|
328
|
|
|
|
|
|
|
} |
|
329
|
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
# CLOSED |
|
331
|
|
|
|
|
|
|
elsif ( $prev_state == CLOSED ) { |
|
332
|
5
|
50
|
33
|
|
|
2431
|
if ( $type != PRIORITY && ( $type != WINDOW_UPDATE && $cln2srv ) ) { |
|
|
|
|
33
|
|
|
|
|
|
333
|
|
|
|
|
|
|
|
|
334
|
0
|
|
|
|
|
0
|
tracer->error("stream is closed\n"); |
|
335
|
0
|
|
|
|
|
0
|
$self->error(STREAM_CLOSED); |
|
336
|
|
|
|
|
|
|
} |
|
337
|
|
|
|
|
|
|
} |
|
338
|
|
|
|
|
|
|
else { |
|
339
|
0
|
|
|
|
|
0
|
tracer->error("oops!\n"); |
|
340
|
0
|
|
|
|
|
0
|
$self->error(INTERNAL_ERROR); |
|
341
|
|
|
|
|
|
|
} |
|
342
|
|
|
|
|
|
|
} |
|
343
|
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
# TODO: move this to some other module |
|
345
|
|
|
|
|
|
|
sub send_headers { |
|
346
|
72
|
|
|
72
|
0
|
3237
|
my ( $self, $stream_id, $headers, $end ) = @_; |
|
347
|
72
|
|
|
|
|
3256
|
my $max_size = $self->enc_setting(SETTINGS_MAX_FRAME_SIZE); |
|
348
|
|
|
|
|
|
|
|
|
349
|
72
|
|
|
|
|
3270
|
my $header_block = headers_encode( $self->encode_context, $headers ); |
|
350
|
|
|
|
|
|
|
|
|
351
|
72
|
100
|
|
|
|
3290
|
my $flags = $end ? END_STREAM : 0; |
|
352
|
72
|
50
|
|
|
|
3290
|
$flags |= END_HEADERS if length($header_block) <= $max_size; |
|
353
|
|
|
|
|
|
|
|
|
354
|
72
|
|
|
|
|
3453
|
$self->enqueue( HEADERS, $flags, $stream_id, |
|
355
|
|
|
|
|
|
|
{ hblock => \substr( $header_block, 0, $max_size, '' ) } ); |
|
356
|
72
|
|
|
|
|
6576
|
while ( length($header_block) > 0 ) { |
|
357
|
0
|
0
|
|
|
|
0
|
my $flags = length($header_block) <= $max_size ? 0 : END_HEADERS; |
|
358
|
0
|
|
|
|
|
0
|
$self->enqueue( CONTINUATION, $flags, |
|
359
|
|
|
|
|
|
|
$stream_id, \substr( $header_block, 0, $max_size, '' ) ); |
|
360
|
|
|
|
|
|
|
} |
|
361
|
|
|
|
|
|
|
} |
|
362
|
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
sub send_pp_headers { |
|
364
|
0
|
|
|
0
|
0
|
0
|
my ( $self, $stream_id, $promised_id, $headers ) = @_; |
|
365
|
0
|
|
|
|
|
0
|
my $max_size = $self->enc_setting(SETTINGS_MAX_FRAME_SIZE); |
|
366
|
|
|
|
|
|
|
|
|
367
|
0
|
|
|
|
|
0
|
my $header_block = headers_encode( $self->encode_context, $headers ); |
|
368
|
|
|
|
|
|
|
|
|
369
|
0
|
0
|
|
|
|
0
|
my $flags = length($header_block) <= $max_size ? END_HEADERS : 0; |
|
370
|
|
|
|
|
|
|
|
|
371
|
0
|
|
|
|
|
0
|
$self->enqueue( PUSH_PROMISE, $flags, $stream_id, |
|
372
|
|
|
|
|
|
|
[ $promised_id, \substr( $header_block, 0, $max_size - 4, '' ) ] ); |
|
373
|
|
|
|
|
|
|
|
|
374
|
0
|
|
|
|
|
0
|
while ( length($header_block) > 0 ) { |
|
375
|
0
|
0
|
|
|
|
0
|
my $flags = length($header_block) <= $max_size ? 0 : END_HEADERS; |
|
376
|
0
|
|
|
|
|
0
|
$self->enqueue( CONTINUATION, $flags, |
|
377
|
|
|
|
|
|
|
$stream_id, \substr( $header_block, 0, $max_size, '' ) ); |
|
378
|
|
|
|
|
|
|
} |
|
379
|
|
|
|
|
|
|
} |
|
380
|
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
sub send_data { |
|
382
|
47
|
|
|
47
|
0
|
3212
|
my ( $self, $stream_id, $chunk, $end ) = @_; |
|
383
|
47
|
|
|
|
|
3295
|
my $data = $self->stream_blocked_data($stream_id); |
|
384
|
47
|
100
|
|
|
|
3277
|
$data .= defined $chunk ? $chunk : ''; |
|
385
|
47
|
100
|
|
|
|
3296
|
$self->stream_end( $stream_id, $end ) if defined $end; |
|
386
|
47
|
|
|
|
|
3260
|
$end = $self->stream_end($stream_id); |
|
387
|
|
|
|
|
|
|
|
|
388
|
47
|
|
|
|
|
3219
|
while (1) { |
|
389
|
51
|
|
|
|
|
3196
|
my $l = length($data); |
|
390
|
51
|
|
|
|
|
3258
|
my $size = $self->enc_setting(SETTINGS_MAX_FRAME_SIZE); |
|
391
|
51
|
|
|
|
|
3286
|
for ( $l, $self->fcw_send, $self->stream_fcw_send($stream_id) ) { |
|
392
|
153
|
100
|
|
|
|
6520
|
$size = $_ if $size > $_; |
|
393
|
|
|
|
|
|
|
} |
|
394
|
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
# Flow control |
|
396
|
51
|
100
|
100
|
|
|
3290
|
last if $l != 0 && $size <= 0; |
|
397
|
50
|
|
|
|
|
3255
|
$self->fcw_send( -$size ); |
|
398
|
50
|
|
|
|
|
3273
|
$self->stream_fcw_send( $stream_id, -$size ); |
|
399
|
|
|
|
|
|
|
|
|
400
|
50
|
100
|
100
|
|
|
3491
|
$self->enqueue( |
|
401
|
|
|
|
|
|
|
DATA, $end && $l == $size ? END_STREAM : 0, |
|
402
|
|
|
|
|
|
|
$stream_id, \substr( $data, 0, $size, '' ) |
|
403
|
|
|
|
|
|
|
); |
|
404
|
50
|
100
|
|
|
|
6449
|
last if $l == $size; |
|
405
|
|
|
|
|
|
|
} |
|
406
|
47
|
|
|
|
|
3281
|
$self->stream_blocked_data( $stream_id, $data ); |
|
407
|
|
|
|
|
|
|
} |
|
408
|
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
sub send_blocked { |
|
410
|
1
|
|
|
1
|
0
|
2
|
my $self = shift; |
|
411
|
1
|
|
|
|
|
2
|
for my $stream_id ( keys %{ $self->{streams} } ) { |
|
|
1
|
|
|
|
|
4
|
|
|
412
|
3
|
|
|
|
|
10
|
$self->stream_send_blocked($stream_id); |
|
413
|
|
|
|
|
|
|
} |
|
414
|
|
|
|
|
|
|
} |
|
415
|
|
|
|
|
|
|
|
|
416
|
|
|
|
|
|
|
sub error { |
|
417
|
0
|
|
|
0
|
0
|
0
|
my $self = shift; |
|
418
|
0
|
0
|
0
|
|
|
0
|
if ( @_ && !$self->{shutdown} ) { |
|
419
|
0
|
|
|
|
|
0
|
$self->{error} = shift; |
|
420
|
0
|
0
|
|
|
|
0
|
$self->{on_error}->( $self->{error} ) if exists $self->{on_error}; |
|
421
|
0
|
|
|
|
|
0
|
$self->finish; |
|
422
|
|
|
|
|
|
|
} |
|
423
|
0
|
|
|
|
|
0
|
$self->{error}; |
|
424
|
|
|
|
|
|
|
} |
|
425
|
|
|
|
|
|
|
|
|
426
|
|
|
|
|
|
|
sub setting { |
|
427
|
0
|
|
|
0
|
0
|
0
|
require Carp; |
|
428
|
0
|
|
|
|
|
0
|
Carp::confess("setting is deprecated\n"); |
|
429
|
|
|
|
|
|
|
} |
|
430
|
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
sub _setting { |
|
432
|
675
|
|
|
675
|
|
38473
|
my ( $ctx, $self, $setting ) = @_; |
|
433
|
675
|
|
|
|
|
38617
|
my $s = $self->{$ctx}->{settings}; |
|
434
|
675
|
50
|
|
|
|
38844
|
return undef unless exists $s->{$setting}; |
|
435
|
675
|
100
|
|
|
|
38775
|
$s->{$setting} = pop if @_ > 3; |
|
436
|
675
|
|
|
|
|
117097
|
$s->{$setting}; |
|
437
|
|
|
|
|
|
|
} |
|
438
|
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
sub enc_setting { |
|
440
|
239
|
|
|
239
|
0
|
12905
|
_setting( 'encode_ctx', @_ ); |
|
441
|
|
|
|
|
|
|
} |
|
442
|
|
|
|
|
|
|
|
|
443
|
|
|
|
|
|
|
sub dec_setting { |
|
444
|
436
|
|
|
436
|
0
|
25874
|
_setting( 'decode_ctx', @_ ); |
|
445
|
|
|
|
|
|
|
} |
|
446
|
|
|
|
|
|
|
|
|
447
|
|
|
|
|
|
|
sub accept_settings { |
|
448
|
33
|
|
|
33
|
0
|
3164
|
my $self = shift; |
|
449
|
33
|
|
|
|
|
3205
|
$self->enqueue( SETTINGS, ACK, 0, {} ); |
|
450
|
|
|
|
|
|
|
} |
|
451
|
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
# Flow control windown of connection |
|
453
|
|
|
|
|
|
|
sub _fcw { |
|
454
|
155
|
|
|
155
|
|
9621
|
my $dir = shift; |
|
455
|
155
|
|
|
|
|
9594
|
my $self = shift; |
|
456
|
|
|
|
|
|
|
|
|
457
|
155
|
100
|
|
|
|
9681
|
if (@_) { |
|
458
|
104
|
|
|
|
|
6451
|
$self->{$dir} += shift; |
|
459
|
104
|
|
|
|
|
6505
|
tracer->debug( "$dir now is " . $self->{$dir} . "\n" ); |
|
460
|
|
|
|
|
|
|
} |
|
461
|
155
|
|
|
|
|
28715
|
$self->{$dir}; |
|
462
|
|
|
|
|
|
|
} |
|
463
|
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
sub fcw_send { |
|
465
|
102
|
|
|
102
|
0
|
6427
|
_fcw( 'fcw_send', @_ ); |
|
466
|
|
|
|
|
|
|
} |
|
467
|
|
|
|
|
|
|
|
|
468
|
|
|
|
|
|
|
sub fcw_recv { |
|
469
|
53
|
|
|
53
|
0
|
3228
|
_fcw( 'fcw_recv', @_ ); |
|
470
|
|
|
|
|
|
|
} |
|
471
|
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
sub fcw_update { |
|
473
|
1
|
|
|
1
|
0
|
2
|
my $self = shift; |
|
474
|
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
# TODO: check size of data in memory |
|
476
|
1
|
|
|
|
|
2
|
my $size = $self->dec_setting(SETTINGS_INITIAL_WINDOW_SIZE); |
|
477
|
1
|
|
|
|
|
2
|
tracer->debug("update fcw recv of connection with $size b.\n"); |
|
478
|
1
|
|
|
|
|
2
|
$self->fcw_recv($size); |
|
479
|
1
|
|
|
|
|
2
|
$self->enqueue( WINDOW_UPDATE, 0, 0, $size ); |
|
480
|
|
|
|
|
|
|
} |
|
481
|
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
sub fcw_initial_change { |
|
483
|
0
|
|
|
0
|
0
|
0
|
my ( $self, $size ) = @_; |
|
484
|
0
|
|
|
|
|
0
|
my $prev_size = $self->enc_setting(SETTINGS_INITIAL_WINDOW_SIZE); |
|
485
|
0
|
|
|
|
|
0
|
my $diff = $size - $prev_size; |
|
486
|
0
|
|
|
|
|
0
|
tracer->debug( |
|
487
|
|
|
|
|
|
|
"Change flow control window on not closed streams with diff $diff\n"); |
|
488
|
0
|
|
|
|
|
0
|
for my $stream_id ( keys %{ $self->{streams} } ) { |
|
|
0
|
|
|
|
|
0
|
|
|
489
|
0
|
0
|
|
|
|
0
|
next if $self->stream_state($stream_id) == CLOSED; |
|
490
|
0
|
|
|
|
|
0
|
$self->stream_fcw_send( $stream_id, $diff ); |
|
491
|
|
|
|
|
|
|
} |
|
492
|
|
|
|
|
|
|
} |
|
493
|
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
sub ack_ping { |
|
495
|
1
|
|
|
1
|
0
|
1
|
my ( $self, $payload_ref ) = @_; |
|
496
|
1
|
|
|
|
|
4
|
$self->enqueue_first( PING, ACK, 0, $payload_ref ); |
|
497
|
|
|
|
|
|
|
} |
|
498
|
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
sub send_ping { |
|
500
|
1
|
|
|
1
|
0
|
2
|
my ( $self, $payload ) = @_; |
|
501
|
1
|
50
|
|
|
|
5
|
if ( !defined $payload ) { |
|
|
|
50
|
|
|
|
|
|
|
502
|
0
|
|
|
|
|
0
|
$payload = pack "C*", map { rand(256) } 1 .. PING_PAYLOAD_SIZE; |
|
|
0
|
|
|
|
|
0
|
|
|
503
|
|
|
|
|
|
|
} |
|
504
|
|
|
|
|
|
|
elsif ( length($payload) != PING_PAYLOAD_SIZE ) { |
|
505
|
0
|
|
|
|
|
0
|
$payload = sprintf "%*.*s", |
|
506
|
|
|
|
|
|
|
-PING_PAYLOAD_SIZE(), PING_PAYLOAD_SIZE, $payload; |
|
507
|
|
|
|
|
|
|
} |
|
508
|
1
|
|
|
|
|
3
|
$self->enqueue( PING, 0, 0, \$payload ); |
|
509
|
|
|
|
|
|
|
} |
|
510
|
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
1; |