File Coverage

blib/lib/Protocol/HTTP2/Frame.pm
Criterion Covered Total %
statement 69 78 88.4
branch 10 22 45.4
condition 6 12 50.0
subroutine 19 19 100.0
pod 0 5 0.0
total 104 136 76.4


line stmt bran cond sub pod time code
1             package Protocol::HTTP2::Frame;
2 12     12   41 use strict;
  12         12  
  12         273  
3 12     12   34 use warnings;
  12         8  
  12         267  
4 12     12   35 use Protocol::HTTP2::Trace qw(tracer);
  12         11  
  12         450  
5             use Protocol::HTTP2::Constants
6 12     12   38 qw(const_name :frame_types :errors :preface :states :flags :limits :settings);
  12         17  
  12         3284  
7 12     12   4033 use Protocol::HTTP2::Frame::Data;
  12         16  
  12         260  
8 12     12   3946 use Protocol::HTTP2::Frame::Headers;
  12         19  
  12         257  
9 12     12   3795 use Protocol::HTTP2::Frame::Priority;
  12         18  
  12         275  
10 12     12   4067 use Protocol::HTTP2::Frame::Rst_stream;
  12         108  
  12         292  
11 12     12   4410 use Protocol::HTTP2::Frame::Settings;
  12         22  
  12         284  
12 12     12   3967 use Protocol::HTTP2::Frame::Push_promise;
  12         16  
  12         259  
13 12     12   3910 use Protocol::HTTP2::Frame::Ping;
  12         19  
  12         243  
14 12     12   3905 use Protocol::HTTP2::Frame::Goaway;
  12         15  
  12         265  
15 12     12   3885 use Protocol::HTTP2::Frame::Window_update;
  12         16  
  12         251  
16 12     12   3998 use Protocol::HTTP2::Frame::Continuation;
  12         16  
  12         6490  
17              
18             # Table of payload decoders
19             my %frame_class = (
20             &DATA => 'Data',
21             &HEADERS => 'Headers',
22             &PRIORITY => 'Priority',
23             &RST_STREAM => 'Rst_stream',
24             &SETTINGS => 'Settings',
25             &PUSH_PROMISE => 'Push_promise',
26             &PING => 'Ping',
27             &GOAWAY => 'Goaway',
28             &WINDOW_UPDATE => 'Window_update',
29             &CONTINUATION => 'Continuation',
30             );
31              
32             my %decoder =
33             map { $_ => \&{ 'Protocol::HTTP2::Frame::' . $frame_class{$_} . '::decode' } }
34             keys %frame_class;
35              
36             my %encoder =
37             map { $_ => \&{ 'Protocol::HTTP2::Frame::' . $frame_class{$_} . '::encode' } }
38             keys %frame_class;
39              
40             sub frame_encode {
41 221     221 0 14893 my ( $con, $type, $flags, $stream_id, $data_ref ) = @_;
42              
43 221         14902 my $payload = $encoder{$type}->( $con, \$flags, $stream_id, $data_ref );
44 221         14398 my $l = length $payload;
45              
46 221         29397 pack( 'CnC2N', ( $l >> 16 ), ( $l & 0xFFFF ), $type, $flags, $stream_id )
47             . $payload;
48             }
49              
50             sub preface_decode {
51 15     15 0 1587 my ( $con, $buf_ref, $buf_offset ) = @_;
52 15 50       1613 return 0 if length($$buf_ref) - $buf_offset < length(PREFACE);
53             return
54 15 50       3185 index( $$buf_ref, PREFACE, $buf_offset ) == -1 ? undef : length(PREFACE);
55             }
56              
57             sub preface_encode {
58 18     18 0 3186 PREFACE;
59             }
60              
61             sub frame_header_decode {
62 320     320 0 22345 my ( undef, $buf_ref, $buf_offset ) = @_;
63              
64 320         22990 my ( $hl, $ll, $type, $flags, $stream_id ) =
65             unpack( 'CnC2N', substr( $$buf_ref, $buf_offset, FRAME_HEADER_SIZE ) );
66              
67 320         22540 my $length = ( $hl << 16 ) + $ll;
68 320         22650 $stream_id &= 0x7FFF_FFFF;
69 320         44597 return $length, $type, $flags, $stream_id;
70             }
71              
72             sub frame_decode {
73 424     424 0 31187 my ( $con, $buf_ref, $buf_offset ) = @_;
74 424 100       55152 return 0 if length($$buf_ref) - $buf_offset < FRAME_HEADER_SIZE;
75              
76 207         14547 my ( $length, $type, $flags, $stream_id ) =
77             $con->frame_header_decode( $buf_ref, $buf_offset );
78              
79 207 50       14654 if ( $length > $con->dec_setting(SETTINGS_MAX_FRAME_SIZE) ) {
80 0         0 tracer->error("Frame is too large: $length\n");
81 0         0 $con->error(FRAME_SIZE_ERROR);
82 0         0 return undef;
83             }
84              
85 207 50       14658 return 0
86             if length($$buf_ref) - $buf_offset - FRAME_HEADER_SIZE - $length < 0;
87              
88             tracer->debug(
89             sprintf "TYPE = %s(%i), FLAGS = %08b, STREAM_ID = %i, "
90             . "LENGTH = %i\n",
91 207 50       14587 exists $frame_class{$type}
92             ? const_name( "frame_types", $type )
93             : "UNKNOWN",
94             $type,
95             $flags,
96             $stream_id,
97             $length
98             );
99              
100 207         14681 my $pending_stream_id = $con->pending_stream;
101 207 0 0     14613 if ( $pending_stream_id
      33        
102             && ( $type != CONTINUATION || $pending_stream_id != $stream_id ) )
103             {
104 0         0 tracer->debug("Expected CONTINUATION for stream $pending_stream_id");
105 0         0 $con->error(PROTOCOL_ERROR);
106 0         0 return undef;
107             }
108              
109             # Unknown type of frame
110 207 50       14573 if ( !exists $frame_class{$type} ) {
111 0         0 tracer->info("ignore unknown frame type $type");
112 0         0 return FRAME_HEADER_SIZE + $length;
113             }
114              
115             $con->decode_context->{frame} = {
116 207         15149 type => $type,
117             flags => $flags,
118             length => $length,
119             stream => $stream_id,
120             };
121              
122             # Try to create new stream structure
123 207 50 100     15019 if ( $stream_id
      66        
124             && !$con->stream($stream_id)
125             && !$con->new_peer_stream($stream_id) )
126             {
127 0 0       0 return $con->error ? undef : FRAME_HEADER_SIZE + $length;
128             }
129              
130             return undef
131 207 50       15145 unless defined $decoder{$type}
132             ->( $con, $buf_ref, $buf_offset + FRAME_HEADER_SIZE, $length );
133              
134             # Arrived frame may change state of stream
135 207         14727 $con->state_machine( 'recv', $type, $flags, $stream_id );
136              
137 207         29112 return FRAME_HEADER_SIZE + $length;
138             }
139              
140             =pod
141              
142             =head1 NOTES
143              
144             =head2 Frame Types vs Flags and Stream ID
145              
146             Table represent possible combination of frame types and flags.
147             Last column -- Stream ID of frame types (x -- sid >= 1, 0 -- sid = 0)
148              
149              
150             +-END_STREAM 0x1
151             | +-ACK 0x1
152             | | +-END_HEADERS 0x4
153             | | | +-PADDED 0x8
154             | | | | +-PRIORITY 0x20
155             | | | | | +-stream id (value)
156             | | | | | |
157             | frame type\flag | V | V | V | V | V | | V |
158             | --------------- |:-:|:-:|:-:|:-:|:-:| - |:---:|
159             | DATA | x | | | x | | | x |
160             | HEADERS | x | | x | x | x | | x |
161             | PRIORITY | | | | | | | x |
162             | RST_STREAM | | | | | | | x |
163             | SETTINGS | | x | | | | | 0 |
164             | PUSH_PROMISE | | | x | x | | | x |
165             | PING | | x | | | | | 0 |
166             | GOAWAY | | | | | | | 0 |
167             | WINDOW_UPDATE | | | | | | | 0/x |
168             | CONTINUATION | | | x | x | | | x |
169              
170             =cut
171              
172             1;