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; |