Build.PL100644000764000764 45513562447335 14605 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10# ========================================================================= # THIS FILE IS AUTOMATICALLY GENERATED BY MINILLA. # DO NOT EDIT DIRECTLY. # ========================================================================= use 5.008_001; use strict; use Module::Build::Tiny 0.035; Build_PL(); Changes100644000764000764 1434113562447335 14643 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10Revision history for Perl extension Protocol-HTTP2 1.10 2019-11-12T06:19:05Z - test: fixed test 9 (issue 10) 1.09 2018-08-05T16:03:20Z - doc: fix spelling mistakes (Gregor Herrmann) - doc: added link to RFC 7541 (Mohammad S Anwar) - bugfix: protect against "disappearing" on_cancel() callback of server object (Felipe Gasper) - bugfix: prevent uninitialized warning (Junho Choi) 1.08 2016-09-27T12:57:26Z - implemented on_error callback for request - fixed bug: incorrect handling of negative window size (thanks to Daniil Bondarev for patch #2) - fixed bug: last chunk of blocked data can be sent several times - size of flow control window updated with current value of SETTINGS_INITIAL_WINDOW_SIZE 1.07 2016-03-03T20:44:19Z - implemented ping() method for client and server - implemented trailer headers support - fixed some error codes - improved header table size handling 1.06 2016-02-22T08:56:19Z - implemented keepalive option for client (#1) - explicit connection closing for client (#1) - fixed MAX_PAYLOAD_SIZE constant value (thanks to Francisco Obispo for bugreport) 1.05 2015-12-24T12:40:10Z - support for request body - new client/server examples with IO::Socket::SSL 1.04 2015-07-10T20:19:19Z - fixed bug: Chrome send ':path' as literal header, make exception for pseudo headers in header check - make exceptions for RST_STREAM frames in state_machine - fixed debuging level 1.03 2015-07-09T21:09:54Z - reworked enqueue() method, implemented enqueue_raw() - return error when CONTINUATION frames interrupted by other frames - check length of RST_STREAM and WINDOW_UPDATE frames - implemented validation rules for settings SETTINGS_ENABLE_PUSH and SETTINGS_INITIAL_WINDOW_SIZE - update flow control window size on active streams when receive SETTINGS_INITIAL_WINDOW_SIZE - fixed bug: now send ack on empty settings - fixed bug: flow control window for sended frames used to be initialized with wrong value - strict validation of headers - check for explicit content-length header to match size of received DATA frames - control for maximum concurent streams - fixed tests 1.02 2015-06-22T17:27:01Z - fixed leaks test 1.01 2015-06-21T14:17:54Z - fixed leaks in Server/Client code - new test to check leaks - updated examples with tls 1.00 2015-05-16T18:51:09Z - HTTP/2 is RFC 7540 - HPACK is RFC 7541 - updated protocol id string ("h2", "h2c"), dropped old interop id strings 0.16 2015-04-05T20:41:49Z - update status (beta) - add wiki link - implemented server streaming - implemented client downloading, request cancelling 0.15 2015-02-26T20:39:20Z - Splited settings for decoder/encoder - Allow to setup custom settings in Server/Client constructor - Fixed bug with settings packing/unpacking - Dropper Log::Dispatch dependency - updated HPACK to draft 12 0.14 2015-02-11T14:03:22Z - updated HTTP/2 to draft 17 - updated HPACK to draft 11 0.13 2014-12-01T07:56:43Z - updated HTTP/2 to draft 16 - added draft_interop version (14) for interoperability 0.12 2014-10-28T12:18:22Z - updated HTTP/2 to draft 15 0.11 2014-08-14T12:07:48Z - dropped Hash::MultiValue requirement - fixed HPACK - fixed HPACK test 0.10 2014-07-31T21:25:59Z - updated HTTP/2 to draft 14 - updated HPACK to draft 09 - fixed tests 0.09 2014-07-08T13:16:24Z - another fix for 09_client_server_tcp.t (check features of Net::SSLeay) - updated extract_* scripts - updated HTTP/2 to draft 13 - removed ALTSVC and BLOCKED frames - removed DATA frames comression support - PAD_HIGH, PAD_LOW flags are replaced by PADDED - settings changed from 8-bit to 16-bit unsigned integer - updated HPACK to draft 08 - updated huffman codes table - updated static table - fixed tests 0.08 2014-05-17T09:59:07Z - fixed test 09_client_server_tcp.t - fixed *_COMPRESS_DATA constants - fixed blocked data handling - allow zero-sized DATA frames - fixed HPACK encoding: evicting and refrence set emptying - added Protocol::HTTP2::Server POD - fixed upgrade (added required header :scheme) 0.07 2014-05-15T13:14:32Z - implemented PRIOIRITY encoder/decoder - update HEADERS implementation (priority handling) - remove old flags PRIORITY_GROUP, PRIORITY_DEPENDENCY - added tcp test - update cpanfile (TCP::Test and other test deps) - implemented ALTSVC encoder/decoder - updated Protocol::HTTP2 POD - added Protocol::HTTP2::Client POD 0.06 2014-05-13T17:51:16Z - switch to Module::Build::Tiny - implemented PING encoder/decoder - fixed Rst_stream - unneeded state manipulation - internal PH2Test test module - implemented PUSH_PROMISE encoder - implemented push for Server - add Server's push in server-tls-anyevent.pl example - process state of encoded frame after putting it on a queue 0.05 2014-05-11T11:19:57Z - implemented flow control - implemented WINDOW_UPDATE encoder/decoder - fixed MAX_PAYLOAD_SIZE constant - fixed runtime error in RST_STREAM - required MIME::Base64 >= 3.11 (encode_base64url and decode_base64url) - HTTP/1.1 Upgrade for client 0.04 2014-05-08T18:22:24Z - enable Upgrade in server-anyevent.pl example - implemented HTTP/1.1 Upgrade (server) - fixed build/tests on windows - update cpanfile (Net::SSLeay > 1.45 for NPN) - update state doc 0.03 2014-05-07T18:05:50Z - client-tls-anyevent.pl with NPN/ALPN support and server's push handling - fixed error handling (send only one GOAWAY) - fixed PUSH_RPOMISE/CONTINUATION state and headers handling - implemented PUSH_PROMISE decoder - implemented RST_STREAM encoder - server-tls-anyevent.pl with NPN/ALPN support - fixed Connection's send(): set END_STREAM flag for last DATA frame - fixed HEADERS/CONTINUATION logic - pending state change until all CONTINUATION frames received - fixed author 0.02 2014-05-05T20:24:31Z - implemented CONTINUATION frame decoding - docs: table about frame types, flags and stream id 0.01 2014-04-27T08:51:15Z - original version LICENSE100644000764000764 4374613562447335 14370 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10This software is copyright (c) 2014 by Vladimir Lettiev . This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself. Terms of the Perl programming language system itself a) the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version, or b) the "Artistic License" --- The GNU General Public License, Version 1, February 1989 --- This software is Copyright (c) 2014 by Vladimir Lettiev . This is free software, licensed under: The GNU General Public License, Version 1, February 1989 GNU GENERAL PUBLIC LICENSE Version 1, February 1989 Copyright (C) 1989 Free Software Foundation, Inc. 51 Franklin St, Suite 500, Boston, MA 02110-1335 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The license agreements of most software companies try to keep users at the mercy of those companies. By contrast, our General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. The General Public License applies to the Free Software Foundation's software and to any other program whose authors commit to using it. You can use it for your programs, too. When we speak of free software, we are referring to freedom, not price. Specifically, the General Public License is designed to make sure that you have the freedom to give away or sell copies of free software, that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of a such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must tell them their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any work containing the Program or a portion of it, either verbatim or with modifications. Each licensee is addressed as "you". 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this General Public License and to the absence of any warranty; and give any other recipients of the Program a copy of this General Public License along with the Program. You may charge a fee for the physical act of transferring a copy. 2. You may modify your copy or copies of the Program or any portion of it, and copy and distribute such modifications under the terms of Paragraph 1 above, provided that you also do the following: a) cause the modified files to carry prominent notices stating that you changed the files and the date of any change; and b) cause the whole of any work that you distribute or publish, that in whole or in part contains the Program or any part thereof, either with or without modifications, to be licensed at no charge to all third parties under the terms of this General Public License (except that you may choose to grant warranty protection to some or all third parties, at your option). c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the simplest and most usual way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this General Public License. d) You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. Mere aggregation of another independent work with the Program (or its derivative) on a volume of a storage or distribution medium does not bring the other work under the scope of these terms. 3. You may copy and distribute the Program (or a portion or derivative of it, under Paragraph 2) in object code or executable form under the terms of Paragraphs 1 and 2 above provided that you also do one of the following: a) accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Paragraphs 1 and 2 above; or, b) accompany it with a written offer, valid for at least three years, to give any third party free (except for a nominal charge for the cost of distribution) a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Paragraphs 1 and 2 above; or, c) accompany it with the information you received as to where the corresponding source code may be obtained. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form alone.) Source code for a work means the preferred form of the work for making modifications to it. For an executable file, complete source code means all the source code for all modules it contains; but, as a special exception, it need not include source code for modules which are standard libraries that accompany the operating system on which the executable file runs, or for standard header files or definitions files that accompany that operating system. 4. You may not copy, modify, sublicense, distribute or transfer the Program except as expressly provided under this General Public License. Any attempt otherwise to copy, modify, sublicense, distribute or transfer the Program is void, and will automatically terminate your rights to use the Program under this License. However, parties who have received copies, or rights to use copies, from you under this General Public License will not have their licenses terminated so long as such parties remain in full compliance. 5. By copying, distributing or modifying the Program (or any work based on the Program) you indicate your acceptance of this license to do so, and all its terms and conditions. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. 7. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of the license which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the license, you may choose any version ever published by the Free Software Foundation. 8. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS Appendix: How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to humanity, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 1, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19xx name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (a program to direct compilers to make passes at assemblers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice That's all there is to it! --- The Artistic License 1.0 --- This software is Copyright (c) 2014 by Vladimir Lettiev . This is free software, licensed under: The Artistic License 1.0 The Artistic License Preamble The intent of this document is to state the conditions under which a Package may be copied, such that the Copyright Holder maintains some semblance of artistic control over the development of the package, while giving the users of the package the right to use and distribute the Package in a more-or-less customary fashion, plus the right to make reasonable modifications. Definitions: - "Package" refers to the collection of files distributed by the Copyright Holder, and derivatives of that collection of files created through textual modification. - "Standard Version" refers to such a Package if it has not been modified, or has been modified in accordance with the wishes of the Copyright Holder. - "Copyright Holder" is whoever is named in the copyright or copyrights for the package. - "You" is you, if you're thinking about copying or distributing this Package. - "Reasonable copying fee" is whatever you can justify on the basis of media cost, duplication charges, time of people involved, and so on. (You will not be required to justify it to the Copyright Holder, but only to the computing community at large as a market that must bear the fee.) - "Freely Available" means that no fee is charged for the item itself, though there may be fees involved in handling the item. It also means that recipients of the item may redistribute it under the same conditions they received it. 1. You may make and give away verbatim copies of the source form of the Standard Version of this Package without restriction, provided that you duplicate all of the original copyright notices and associated disclaimers. 2. You may apply bug fixes, portability fixes and other modifications derived from the Public Domain or from the Copyright Holder. A Package modified in such a way shall still be considered the Standard Version. 3. You may otherwise modify your copy of this Package in any way, provided that you insert a prominent notice in each changed file stating how and when you changed that file, and provided that you do at least ONE of the following: a) place your modifications in the Public Domain or otherwise make them Freely Available, such as by posting said modifications to Usenet or an equivalent medium, or placing the modifications on a major archive site such as ftp.uu.net, or by allowing the Copyright Holder to include your modifications in the Standard Version of the Package. b) use the modified Package only within your corporation or organization. c) rename any non-standard executables so the names do not conflict with standard executables, which must also be provided, and provide a separate manual page for each non-standard executable that clearly documents how it differs from the Standard Version. d) make other distribution arrangements with the Copyright Holder. 4. You may distribute the programs of this Package in object code or executable form, provided that you do at least ONE of the following: a) distribute a Standard Version of the executables and library files, together with instructions (in the manual page or equivalent) on where to get the Standard Version. b) accompany the distribution with the machine-readable source of the Package with your modifications. c) accompany any non-standard executables with their corresponding Standard Version executables, giving the non-standard executables non-standard names, and clearly documenting the differences in manual pages (or equivalent), together with instructions on where to get the Standard Version. d) make other distribution arrangements with the Copyright Holder. 5. You may charge a reasonable copying fee for any distribution of this Package. You may charge any fee you choose for support of this Package. You may not charge a fee for this Package itself. However, you may distribute this Package in aggregate with other (possibly commercial) programs as part of a larger (possibly commercial) software distribution provided that you do not advertise this Package as a product of your own. 6. The scripts and library files supplied as input to or produced as output from the programs of this Package do not automatically fall under the copyright of this Package, but belong to whomever generated them, and may be sold commercially, and may be aggregated with this Package. 7. C or perl subroutines supplied by you and linked into this Package shall not be considered part of this Package. 8. The name of the Copyright Holder may not be used to endorse or promote products derived from this software without specific prior written permission. 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. The End META.json100644000764000764 1123013562447335 14763 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10{ "abstract" : "HTTP/2 protocol implementation (RFC 7540)", "author" : [ "Vladimir Lettiev " ], "dynamic_config" : 0, "generated_by" : "Minilla/v3.1.7", "license" : [ "perl_5" ], "meta-spec" : { "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", "version" : "2" }, "name" : "Protocol-HTTP2", "no_index" : { "directory" : [ "t", "xt", "inc", "share", "eg", "examples", "author", "builder" ] }, "prereqs" : { "configure" : { "requires" : { "Module::Build::Tiny" : "0.035" } }, "develop" : { "requires" : { "AnyEvent" : "0", "Net::SSLeay" : "> 1.45", "Test::CPAN::Meta" : "0", "Test::MinimumVersion::Fast" : "0.04", "Test::PAUSE::Permissions" : "0.07", "Test::Pod" : "1.41", "Test::Spellunker" : "v0.2.7", "XML::LibXML" : "0" } }, "runtime" : { "requires" : { "MIME::Base64" : "3.11", "Scalar::Util" : "0", "perl" : "5.008005" } }, "test" : { "requires" : { "AnyEvent" : "0", "Net::SSLeay" : "> 1.45", "Test::LeakTrace" : "0", "Test::More" : "0.98", "Test::TCP" : "0" } } }, "provides" : { "Protocol::HTTP2" : { "file" : "lib/Protocol/HTTP2.pm", "version" : "1.10" }, "Protocol::HTTP2::Client" : { "file" : "lib/Protocol/HTTP2/Client.pm" }, "Protocol::HTTP2::Connection" : { "file" : "lib/Protocol/HTTP2/Connection.pm" }, "Protocol::HTTP2::Constants" : { "file" : "lib/Protocol/HTTP2/Constants.pm" }, "Protocol::HTTP2::Frame" : { "file" : "lib/Protocol/HTTP2/Frame.pm" }, "Protocol::HTTP2::Frame::Continuation" : { "file" : "lib/Protocol/HTTP2/Frame/Continuation.pm" }, "Protocol::HTTP2::Frame::Data" : { "file" : "lib/Protocol/HTTP2/Frame/Data.pm" }, "Protocol::HTTP2::Frame::Goaway" : { "file" : "lib/Protocol/HTTP2/Frame/Goaway.pm" }, "Protocol::HTTP2::Frame::Headers" : { "file" : "lib/Protocol/HTTP2/Frame/Headers.pm" }, "Protocol::HTTP2::Frame::Ping" : { "file" : "lib/Protocol/HTTP2/Frame/Ping.pm" }, "Protocol::HTTP2::Frame::Priority" : { "file" : "lib/Protocol/HTTP2/Frame/Priority.pm" }, "Protocol::HTTP2::Frame::Push_promise" : { "file" : "lib/Protocol/HTTP2/Frame/Push_promise.pm" }, "Protocol::HTTP2::Frame::Rst_stream" : { "file" : "lib/Protocol/HTTP2/Frame/Rst_stream.pm" }, "Protocol::HTTP2::Frame::Settings" : { "file" : "lib/Protocol/HTTP2/Frame/Settings.pm" }, "Protocol::HTTP2::Frame::Window_update" : { "file" : "lib/Protocol/HTTP2/Frame/Window_update.pm" }, "Protocol::HTTP2::HeaderCompression" : { "file" : "lib/Protocol/HTTP2/HeaderCompression.pm" }, "Protocol::HTTP2::Huffman" : { "file" : "lib/Protocol/HTTP2/Huffman.pm" }, "Protocol::HTTP2::HuffmanCodes" : { "file" : "lib/Protocol/HTTP2/HuffmanCodes.pm" }, "Protocol::HTTP2::Server" : { "file" : "lib/Protocol/HTTP2/Server.pm" }, "Protocol::HTTP2::Server::Stream" : { "file" : "lib/Protocol/HTTP2/Server.pm" }, "Protocol::HTTP2::StaticTable" : { "file" : "lib/Protocol/HTTP2/StaticTable.pm" }, "Protocol::HTTP2::Stream" : { "file" : "lib/Protocol/HTTP2/Stream.pm" }, "Protocol::HTTP2::Trace" : { "file" : "lib/Protocol/HTTP2/Trace.pm" }, "Protocol::HTTP2::Upgrade" : { "file" : "lib/Protocol/HTTP2/Upgrade.pm" } }, "release_status" : "stable", "resources" : { "bugtracker" : { "web" : "https://github.com/vlet/p5-Protocol-HTTP2/issues" }, "homepage" : "https://github.com/vlet/p5-Protocol-HTTP2", "repository" : { "url" : "git://github.com/vlet/p5-Protocol-HTTP2.git", "web" : "https://github.com/vlet/p5-Protocol-HTTP2" } }, "version" : "1.10", "x_contributors" : [ "Daniil Bondarev ", "Felipe Gasper ", "Junho Choi ", "Mohammad S Anwar ", "gregor herrmann " ], "x_serialization_backend" : "JSON::PP version 4.04", "x_static_install" : 1 } README.md100644000764000764 775413562447335 14621 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10# NAME Protocol::HTTP2 - HTTP/2 protocol implementation (RFC 7540) # SYNOPSIS use Protocol::HTTP2; # get protocol identification string for secure connections print Protocol::HTTP2::ident_tls; # h2 # get protocol identification string for non-secure connections print Protocol::HTTP2::ident_plain; # h2c # DESCRIPTION Protocol::HTTP2 is HTTP/2 protocol implementation ([RFC 7540](https://tools.ietf.org/html/rfc7540)) with stateful decoders/encoders of HTTP/2 frames. You may use this module to implement your own HTTP/2 client/server/intermediate on top of your favorite event loop over plain or tls socket (see examples). # STATUS Current status - beta. Structures, module names and methods seems like stable. I've started this project to understand internals of HTTP/2 and may be it will never become production, but at least it works. | Spec | status | | ----------------------- | --------------- | | Negotiation | ALPN, NPN, | | | Upgrade, direct | | Preface | + | | Headers (de)compression | + | | Stream states | + | | Flow control | ± | | Stream priority | ± | | Server push | + | | Connect method | - | | Frame | encoder | decoder | | --------------- |:-------:|:-------:| | DATA | ± | + | | HEADERS | + | + | | PRIORITY | + | + | | RST_STREAM | + | + | | SETTINGS | + | + | | PUSH_PROMISE | + | + | | PING | + | + | | GOAWAY | + | + | | WINDOW_UPDATE | + | + | | CONTINUATION | ± | + | - - -- not implemeted - ± -- incomplete - + -- implemented (may even work) # MODULES ## [Protocol::HTTP2::Client](https://metacpan.org/pod/Protocol%3A%3AHTTP2%3A%3AClient) Client protocol decoder/encoder with constructor of requests ## [Protocol::HTTP2::Server](https://metacpan.org/pod/Protocol%3A%3AHTTP2%3A%3AServer) Server protocol decoder/encoder with constructor of responses/pushes ## [Protocol::HTTP2::Connection](https://metacpan.org/pod/Protocol%3A%3AHTTP2%3A%3AConnection) Main low level module for protocol logic and state processing. Connection object is a mixin of [Protocol::HTTP2::Frame](https://metacpan.org/pod/Protocol%3A%3AHTTP2%3A%3AFrame) (frame encoding/decoding), [Protocol::HTTP2::Stream](https://metacpan.org/pod/Protocol%3A%3AHTTP2%3A%3AStream) (stream operations) and [Protocol::HTTP2::Upgrade](https://metacpan.org/pod/Protocol%3A%3AHTTP2%3A%3AUpgrade) (HTTP/1.1 Upgrade support) ## [Protocol::HTTP2::HeaderCompression](https://metacpan.org/pod/Protocol%3A%3AHTTP2%3A%3AHeaderCompression) Module implements HPACK - Header Compression for HTTP/2 ([RFC 7541](https://tools.ietf.org/html/rfc7541)). ## [Protocol::HTTP2::Constants](https://metacpan.org/pod/Protocol%3A%3AHTTP2%3A%3AConstants) Module contains all defined in HTTP/2 protocol constants and default values ## [Protocol::HTTP2::Trace](https://metacpan.org/pod/Protocol%3A%3AHTTP2%3A%3ATrace) Module for debugging. You can setup HTTP2\_DEBUG environment variable to change verbosity of the module (output to STDOUT). Default level is error. $ export HTTP2_DEBUG=debug $ perl ./http2_program # SEE ALSO [https://github.com/vlet/p5-Protocol-HTTP2/wiki](https://github.com/vlet/p5-Protocol-HTTP2/wiki) - Protocol::HTTP2 wiki [http://http2.github.io/](http://http2.github.io/) - official HTTP/2 specification site [http://daniel.haxx.se/http2/](http://daniel.haxx.se/http2/) - http2 explained # LICENSE Copyright (C) Vladimir Lettiev. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. # AUTHOR Vladimir Lettiev cpanfile100644000764000764 64613562447335 15017 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10requires 'perl', '5.008001'; # (encode,decode)_base64url requires 'MIME::Base64', '3.11'; # weaken requires 'Scalar::Util'; on 'test' => sub { requires 'Test::More', '0.98'; requires 'AnyEvent'; requires 'Net::SSLeay', '> 1.45'; requires 'Test::TCP'; requires 'Test::LeakTrace'; }; on 'develop' => sub { requires 'XML::LibXML'; requires 'AnyEvent'; requires 'Net::SSLeay', '> 1.45'; }; client-anyevent.pl100644000764000764 410213562447335 20602 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/examplesuse strict; use warnings; use AnyEvent; use AnyEvent::Socket; use AnyEvent::Handle; use Protocol::HTTP2::Client; use Protocol::HTTP2::Constants qw(const_name); my $client = Protocol::HTTP2::Client->new( on_change_state => sub { my ( $stream_id, $previous_state, $current_state ) = @_; printf "Stream %i changed state from %s to %s\n", $stream_id, const_name( "states", $previous_state ), const_name( "states", $current_state ); }, on_error => sub { my $error = shift; printf "Error occured: %s\n", const_name( "errors", $error ); }, # Perform HTTP/1.1 Upgrade upgrade => 1, ); my $host = '127.0.0.1'; my $port = 8000; # Prepare http/2 request $client->request( ':scheme' => "http", ':authority' => $host . ":" . $port, ':path' => "/minil.toml", ':method' => "GET", headers => [ 'accept' => '*/*', 'user-agent' => 'perl-Protocol-HTTP2/0.01', ], on_done => sub { my ( $headers, $data ) = @_; printf "Get headers. Count: %i\n", scalar(@$headers) / 2; printf "Get data. Length: %i\n", length($data); print $data; }, ); my $w = AnyEvent->condvar; tcp_connect $host, $port, sub { my ($fh) = @_ or die "connection failed: $!"; my $handle; $handle = AnyEvent::Handle->new( fh => $fh, autocork => 1, on_error => sub { $_[0]->destroy; print "connection error\n"; $w->send; }, on_eof => sub { $handle->destroy; $w->send; } ); # First write preface to peer while ( my $frame = $client->next_frame ) { $handle->push_write($frame); } $handle->on_read( sub { my $handle = shift; $client->feed( $handle->{rbuf} ); $handle->{rbuf} = undef; while ( my $frame = $client->next_frame ) { $handle->push_write($frame); } $handle->push_shutdown if $client->shutdown; } ); }; $w->recv; client-io-socket-ssl.pl100644000764000764 240713562447335 21453 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/examplesuse strict; use warnings; use Protocol::HTTP2::Client; use IO::Socket::SSL; use IO::Select; my $host = 'example.com'; my $port = 443; # POST request my $h2_client = Protocol::HTTP2::Client->new->request( # HTTP/2-headers ':method' => 'POST', ':path' => '/api/datas', ':scheme' => 'https', ':authority' => $host . ':' . $port, # HTTP-headers headers => [ 'user-agent' => 'Protocol::HTTP2', 'content-type' => 'application/json' ], # do something useful with data on_done => sub { my ( $headers, $data ) = @_; }, # POST body data => '{ "data" : "test" }', ); # TLS transport socket my $client = IO::Socket::SSL->new( PeerHost => $host, PeerPort => $port, # openssl 1.0.1 support only NPN SSL_npn_protocols => ['h2'], # openssl 1.0.2 also have ALPN #SSL_alpn_protocols => ['h2'], ) or die $!; # non blocking $client->blocking(0); my $sel = IO::Select->new($client); # send/recv frames until request is done while ( !$h2_client->shutdown ) { $sel->can_write; while ( my $frame = $h2_client->next_frame ) { syswrite $client, $frame; } $sel->can_read; while ( sysread $client, my $data, 4096 ) { $h2_client->feed($data); } } client-tls-anyevent.pl100644000764000764 645713562447335 21421 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/examplesuse strict; use warnings; use AnyEvent; use AnyEvent::Socket; use AnyEvent::Handle; use Net::SSLeay; use AnyEvent::TLS; use Protocol::HTTP2; use Protocol::HTTP2::Client; use Protocol::HTTP2::Constants qw(const_name); Net::SSLeay::initialize(); my $client = Protocol::HTTP2::Client->new( on_change_state => sub { my ( $stream_id, $previous_state, $current_state ) = @_; printf "Stream %i changed state from %s to %s\n", $stream_id, const_name( "states", $previous_state ), const_name( "states", $current_state ); }, on_push => sub { my ($push_headers) = @_; # If we accept PUSH_PROMISE # return callback to receive promised data # return undef otherwise print "Server want to push some resource to us\n"; return sub { my ( $headers, $data ) = @_; print "Received promised resource\n"; } }, on_error => sub { my $error = shift; printf "Error occured: %s\n", const_name( "errors", $error ); } ); my $host = '127.0.0.1'; my $port = 8000; # Prepare http/2 request $client->request( ':scheme' => "https", ':authority' => $host . ":" . $port, ':path' => "/minil.toml", ':method' => "GET", headers => [ 'accept' => '*/*', 'user-agent' => 'perl-Protocol-HTTP2/0.01', ], on_done => sub { my ( $headers, $data ) = @_; printf "Get headers. Count: %i\n", scalar(@$headers) / 2; printf "Get data. Length: %i\n", length($data); print $data; }, ); my $w = AnyEvent->condvar; tcp_connect $host, $port, sub { my ($fh) = @_ or do { print "connection failed: $!\n"; $w->send; return; }; my $tls; eval { $tls = AnyEvent::TLS->new( method => "TLSv1_2", ); # ALPN (Net-SSLeay > 1.55, openssl >= 1.0.2) if ( exists &Net::SSLeay::CTX_set_alpn_protos ) { Net::SSLeay::CTX_set_alpn_protos( $tls->ctx, [Protocol::HTTP2::ident_tls] ); } # NPN (Net-SSLeay > 1.45, openssl >= 1.0.1) elsif ( exists &Net::SSLeay::CTX_set_next_proto_select_cb ) { Net::SSLeay::CTX_set_next_proto_select_cb( $tls->ctx, [Protocol::HTTP2::ident_tls] ); } else { die "ALPN and NPN is not supported\n"; } }; if ($@) { print "Some problem with SSL CTX: $@\n"; $w->send; return; } my $handle; $handle = AnyEvent::Handle->new( fh => $fh, tls => "connect", tls_ctx => $tls, autocork => 1, on_error => sub { $_[0]->destroy; print "connection error\n"; $w->send; }, on_eof => sub { $handle->destroy; $w->send; } ); # First write preface to peer while ( my $frame = $client->next_frame ) { $handle->push_write($frame); } $handle->on_read( sub { my $handle = shift; $client->feed( $handle->{rbuf} ); $handle->{rbuf} = undef; while ( my $frame = $client->next_frame ) { $handle->push_write($frame); } $handle->push_shutdown if $client->shutdown; } ); }; $w->recv; extract_huff_codes.pl100644000764000764 175613562447335 21350 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/examples#!/usr/bin/perl # use strict; use warnings; use XML::LibXML; sub usage { "Usage: $0 draft-ietf-httpbis-header-compression.xml\n"; } my $file = $ARGV[0] or die usage; die usage unless -f $file; open my $fh, '<', $file or die $!; my $doc = XML::LibXML->load_xml( IO => $fh ); my $hufftable = XML::LibXML::XPathExpression->new( '//section[@title="Huffman Code"]//artwork'); my $value = $doc->findvalue($hufftable); die "cant find Huffman Codes section" unless $value; print << 'EOF'; package Protocol::HTTP2::HuffmanCodes; use strict; use warnings; require Exporter; our @ISA = qw(Exporter); our ( %hcodes, %rhcodes, $hre ); our @EXPORT = qw(%hcodes %rhcodes $hre); %hcodes = ( EOF for ( split /\n/, $value ) { my ( $code, $hex, $bit ) = (/\((.{3})\).+\s([0-9a-f]+)\s+\[\s*(\d+)\]/) or next; printf " %3d => '%0${bit}b',\n", $code, hex($hex); } print << 'EOF'; ); %rhcodes = reverse %hcodes; { local $" = '|'; $hre = qr/(?:^|\G)(@{[ keys %rhcodes ]})/; } 1; EOF extract_static_table.pl100644000764000764 206613562447335 21674 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/examples#!/usr/bin/perl # use strict; use warnings; use XML::LibXML; sub usage { "Usage: $0 draft-ietf-httpbis-header-compression.xml\n"; } my $file = $ARGV[0] or die usage; die usage unless -f $file; open my $fh, '<', $file or die $!; my $doc = XML::LibXML->load_xml( IO => $fh ); my $stattable = XML::LibXML::XPathExpression->new( '//texttable[@title="Static Table Entries"]/c'); my @nodes = $doc->findnodes($stattable); print <<'EOF'; package Protocol::HTTP2::StaticTable; use strict; use warnings; require Exporter; our @ISA = qw(Exporter); our ( @stable, %rstable ); our @EXPORT = qw(@stable %rstable); @stable = ( EOF while (@nodes) { my ( $idx, $name, $value ) = map { $_->textContent } splice( @nodes, 0, 3 ); last unless $idx; printf qq{ [ "%s", "%s" ],\n}, $name, $value; } print <<'EOF'; ); for my $k ( 0 .. $#stable ) { my $key = join ' ', @{ $stable[$k] }; $rstable{$key} = $k + 1; $rstable{ $stable[$k]->[0] . ' ' } = $k + 1 if ( $stable[$k]->[1] ne '' && !exists $rstable{ $stable[$k]->[0] . ' ' } ); } 1; EOF server-anyevent.pl100644000764000764 437113562447335 20642 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/examplesuse strict; use warnings; use AnyEvent; use AnyEvent::Socket; use AnyEvent::Handle; use Protocol::HTTP2::Server; use Protocol::HTTP2::Constants qw(const_name); my $host = '127.0.0.1'; my $port = 8000; my $w = AnyEvent->condvar; tcp_server $host, $port, sub { my ( $fh, $host, $port ) = @_; my $handle; $handle = AnyEvent::Handle->new( fh => $fh, autocork => 1, on_error => sub { $_[0]->destroy; print "connection error\n"; }, on_eof => sub { $handle->destroy; } ); my $server; $server = Protocol::HTTP2::Server->new( on_change_state => sub { my ( $stream_id, $previous_state, $current_state ) = @_; printf "Stream %i changed state from %s to %s\n", $stream_id, const_name( "states", $previous_state ), const_name( "states", $current_state ); }, on_error => sub { my $error = shift; printf "Error occured: %s\n", const_name( "errors", $error ); }, on_request => sub { my ( $stream_id, $headers, $data ) = @_; my $message = "hello, world!"; $server->response( ':status' => 200, stream_id => $stream_id, headers => [ 'server' => 'perl-Protocol-HTTP2/0.01', 'content-length' => length($message), 'cache-control' => 'max-age=3600', 'date' => 'Fri, 18 Apr 2014 07:27:11 GMT', 'last-modified' => 'Thu, 27 Feb 2014 10:30:37 GMT', ], data => $message, ); }, # Accept HTTP/1.1 Upgrade header upgrade => 1, ); # First send settings to peer while ( my $frame = $server->next_frame ) { $handle->push_write($frame); } $handle->on_read( sub { my $handle = shift; $server->feed( $handle->{rbuf} ); $handle->{rbuf} = undef; while ( my $frame = $server->next_frame ) { $handle->push_write($frame); } $handle->push_shutdown if $server->shutdown; } ); }; $w->recv; server-io-socket-ssl.pl100644000764000764 311613562447335 21501 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/examplesuse strict; use warnings; use IO::Select; use IO::Socket::SSL; use Protocol::HTTP2::Server; # TLS transport socket my $srv = IO::Socket::SSL->new( LocalAddr => '0.0.0.0:4443', Listen => 10, SSL_cert_file => 'test.crt', SSL_key_file => 'test.key', # openssl 1.0.1 support only NPN SSL_npn_protocols => ['h2'], # openssl 1.0.2 also have ALPN #SSL_alpn_protocols => ['h2'], ) or die $!; # Accept client connection while ( my $client = $srv->accept ) { # HTTP/2 server my $h2_srv; $h2_srv = Protocol::HTTP2::Server->new( on_request => sub { my ( $stream_id, $headers, $data ) = @_; $h2_srv->response( ':status' => 200, stream_id => $stream_id, headers => [ 'server' => 'Protocol::HTTP2::Server', 'content-type' => 'application/json', ], data => '{ "hello" : "world" }', ); } ); # non-blocking $client->blocking(0); my $sel = IO::Select->new($client); # send/recv frames until request/response is done while ( !$h2_srv->shutdown ) { $sel->can_write; while ( my $frame = $h2_srv->next_frame ) { syswrite $client, $frame; } $sel->can_read; my $len; while ( my $rd = sysread $client, my $data, 4096 ) { $h2_srv->feed($data); $len += $rd; } # check if client disconnects last unless $len; } # destroy server object undef $h2_srv; } server-tls-anyevent.pl100644000764000764 772513562447335 21450 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/examplesuse strict; use warnings; use AnyEvent; use AnyEvent::Socket; use AnyEvent::Handle; use Net::SSLeay; use AnyEvent::TLS; use Protocol::HTTP2; use Protocol::HTTP2::Server; use Protocol::HTTP2::Constants qw(const_name); Net::SSLeay::initialize(); my $host = '127.0.0.1'; my $port = 8000; my $w = AnyEvent->condvar; tcp_server $host, $port, sub { my ( $fh, $host, $port ) = @_; my $handle; my $tls; eval { $tls = AnyEvent::TLS->new( method => "TLSv1_2", cert_file => "test.crt", key_file => "test.key", ); # ECDH curve ( Net-SSLeay >= 1.56, openssl >= 1.0.0 ) if ( exists &Net::SSLeay::CTX_set_tmp_ecdh ) { my $curve = Net::SSLeay::OBJ_txt2nid('prime256v1'); my $ecdh = Net::SSLeay::EC_KEY_new_by_curve_name($curve); Net::SSLeay::CTX_set_tmp_ecdh( $tls->ctx, $ecdh ); Net::SSLeay::EC_KEY_free($ecdh); } # ALPN (Net-SSLeay > 1.55, openssl >= 1.0.2) if ( exists &Net::SSLeay::CTX_set_alpn_select_cb ) { Net::SSLeay::CTX_set_alpn_select_cb( $tls->ctx, [Protocol::HTTP2::ident_tls] ); } # NPN (Net-SSLeay > 1.45, openssl >= 1.0.1) elsif ( exists &Net::SSLeay::CTX_set_next_protos_advertised_cb ) { Net::SSLeay::CTX_set_next_protos_advertised_cb( $tls->ctx, [Protocol::HTTP2::ident_tls] ); } else { die "ALPN and NPN is not supported\n"; } }; if ($@) { print "Some problem with SSL CTX: $@\n"; $w->send; return; } $handle = AnyEvent::Handle->new( fh => $fh, autocork => 1, tls => "accept", tls_ctx => $tls, on_error => sub { $_[0]->destroy; print "connection error\n"; }, on_eof => sub { $handle->destroy; } ); my $server; $server = Protocol::HTTP2::Server->new( on_change_state => sub { my ( $stream_id, $previous_state, $current_state ) = @_; printf "Stream %i changed state from %s to %s\n", $stream_id, const_name( "states", $previous_state ), const_name( "states", $current_state ); }, on_error => sub { my $error = shift; printf "Error occured: %s\n", const_name( "errors", $error ); }, on_request => sub { my ( $stream_id, $headers, $data ) = @_; my %h = (@$headers); # Push promise (must be before response) if ( $h{':path'} eq '/minil.toml' ) { $server->push( ':authority' => $host . ':' . $port, ':method' => 'GET', ':path' => '/css/style.css', ':scheme' => 'https', stream_id => $stream_id, ); } my $message = "hello, world!"; $server->response( ':status' => 200, stream_id => $stream_id, headers => [ 'server' => 'perl-Protocol-HTTP2/0.01', 'content-length' => length($message), 'cache-control' => 'max-age=3600', 'date' => 'Fri, 18 Apr 2014 07:27:11 GMT', 'last-modified' => 'Thu, 27 Feb 2014 10:30:37 GMT', ], data => $message, ); }, ); # First send settings to peer while ( my $frame = $server->next_frame ) { $handle->push_write($frame); } $handle->on_read( sub { my $handle = shift; $server->feed( $handle->{rbuf} ); $handle->{rbuf} = undef; while ( my $frame = $server->next_frame ) { $handle->push_write($frame); } $handle->push_shutdown if $server->shutdown; } ); }; $w->recv; test.crt100644000764000764 251213562447335 16634 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/examples-----BEGIN CERTIFICATE----- MIIDujCCAqKgAwIBAgIUQ+Tzss6ENkikX2v1lEThSAJAxawwDQYJKoZIhvcNAQEL BQAwbjELMAkGA1UEBhMCUlUxEDAOBgNVBAgMB0RlZmF1bHQxFTATBgNVBAcMDERl ZmF1bHQgQ2l0eTEQMA4GA1UECgwHRGVmYXVsdDEQMA4GA1UECwwHZGVmYXVsdDES MBAGA1UEAwwJMTI3LjAuMC4xMB4XDTE5MTAxNDE2NTg0MloXDTI5MTAxMTE2NTg0 MlowbjELMAkGA1UEBhMCUlUxEDAOBgNVBAgMB0RlZmF1bHQxFTATBgNVBAcMDERl ZmF1bHQgQ2l0eTEQMA4GA1UECgwHRGVmYXVsdDEQMA4GA1UECwwHZGVmYXVsdDES MBAGA1UEAwwJMTI3LjAuMC4xMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC AQEArHBqjSEvJg/jYlVgv24LXgkc5WQxGAtSE3R/YdK0wEygSmcQpTFA1WfHPfHn AaHHlZTClfDv7UWNQIwfYqedQJwKPse5NFITw/ig9gONsDxIlBdc5hIqdWuIBQ/l j2Z/j3GIkVe4+vw4pGd5BmiQg98xLkca0/BVLVyCm+65g1oyx1SI4X98TfD3wTKg /KSUzuPaxtiJFZAxh4ayu4Gmyb8TyKKLR9Ff1ePceqVR5egBR+z/PJdKRoQDdb11 x62g3p6tGpPmm3lxP9ovpYZtJNHhH7h6+AP7prAf3qcHPmnLvRAgSDznMqpMLVdO /Za5lY1h3dCkOAUV6Nf2YV26+wIDAQABo1AwTjAdBgNVHQ4EFgQU2wAFWDmSgrR9 1uOlFtQG1V2/s8swHwYDVR0jBBgwFoAU2wAFWDmSgrR91uOlFtQG1V2/s8swDAYD VR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAmIhRxatzArXRwQHndj8DLfuF UG3hPxr1MK2/ooSXHvQqZwJgUXRt92NisBPhtHzPxrh+yPxjHno51kSbihuZUcOI ksjICBROUhdqzqfPnCiFkxmOHzPFEfFqmi17IhQeyve63ABxZdkRZd8wxfP4Ekoj xM3k9nLE7Ud7SajGou6VJ0+kuDxovEgIrllvCVdkBkbl5ILBlsKeBEi/ncz+nmam zOFQ1M6Iw7bD1zi9K5H/KRuYnKpqxA539+N6fW+bA/wnW8DOLnEx+KUUx0Jt55su ot/qTiqmTw2dIm3CC2ypgBmTEfyGV3uHwMU/sVetSOeCagqSkY/I25hx5jC0Mg== -----END CERTIFICATE----- test.key100644000764000764 321313562447335 16633 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/examples-----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEArHBqjSEvJg/jYlVgv24LXgkc5WQxGAtSE3R/YdK0wEygSmcQ pTFA1WfHPfHnAaHHlZTClfDv7UWNQIwfYqedQJwKPse5NFITw/ig9gONsDxIlBdc 5hIqdWuIBQ/lj2Z/j3GIkVe4+vw4pGd5BmiQg98xLkca0/BVLVyCm+65g1oyx1SI 4X98TfD3wTKg/KSUzuPaxtiJFZAxh4ayu4Gmyb8TyKKLR9Ff1ePceqVR5egBR+z/ PJdKRoQDdb11x62g3p6tGpPmm3lxP9ovpYZtJNHhH7h6+AP7prAf3qcHPmnLvRAg SDznMqpMLVdO/Za5lY1h3dCkOAUV6Nf2YV26+wIDAQABAoIBADLxLvksgYJMFU+6 i09iUidgp9G4zKwexAuNUghzOATLXls8oXU73Lxu4TSSnz0jLxQok2e6exbsgjM8 chUyEUnCD2DGnhcv3Dj73YlwOU6EMKjXUhGB8lsn/lIIhTfc/vhAgSj28mXrV0xy aRWUlITwzdWvGeTczj0NZGRunQ2Jfrm/6hYsPe3W8HYETnU592p3gU0p+RB3XEaD MfsGdv9+lG3dUrly9vYxI2R1Ushec02zXut34iuqCTIwC9EFPwzoAL6XnY7047XO iXXZfBWMuSKOL9jfTJb5Q1aXnlhTpVlFyV4JhO/rgcfo801gJThqhDZIEZXigDuB j/dIsckCgYEA3lhih02pYLJiDis83g9OYchBlISDVh8zsSochkt/oiMI9MKhpb/6 rjjqXB+G0TXZ9iJVT0/lXoCSFftN5qgdSjh4pNAaq2qKHd7ogv2Gi94cLWmCdLE8 nKctQgvJB4Q+bBOBbcvl3I9oZQkdj1Ap09CHbWcSLLX9gOrlHBsGf40CgYEAxoo6 X/VO1zu13KDbvyANbXoG/jN6+P1CAc0coW1KmI7j3zS8JS/BNxC8ZIcquvMupCe2 Ya0cXdYMGMQAmJ27T35f7MfDymjEcx3ZmyUEwov4t7OFyk/VkN6dnIUX39yglPMt ossAfNj2EBYAc/l8/p7Tuk0JYxRw1MpN75dmHqcCgYANpFSfQpeS1D8J6YM5iKzh ePz1FNBOF2n/g7ruTnGNTCL/iXWLiuThjaJrdo+6BFjULjUXwaosCy1rZdjYvxXU +PQGALKyM743qPaRGucHa+BEtQWJDVrPrb4sIDb8XBPMY8H8L5dx2eao1E9Y/K0k TtYQU1OdJKliIIdgGxRh/QKBgEpYZqWaOWy1ilNU1RTLztto74dvBaSJSZddFFSK lX1tPH1PxQhzynlxReqrBuA8wgFscYpABbhJt/vqIYMExaht3UPQRkvcUXv9+Id1 JEQpn/hCPF5W6NU313NOD3OfrW45ZaRpOgSGRhYd9wt2qEy8cvJ3eIVmmR3Fp8uJ OQ9PAoGAU5W2dENhzCEg3wNcdXCnna/YuV4UpmjIjDcZduSmZhjbzHP0sHI1uEe8 fndBa+6Klf6mKo2S2DssNldhItUgu0SGNa6DVb7gh0i5X8gaJjj8vWPxtEzOPJhN 3/f0gdWjc2ZBy+a0rMoRiaDFQIokHjitiDR+P3Ct70b3/Il7QHs= -----END RSA PRIVATE KEY----- HTTP2.pm100644000764000764 722513562447335 17101 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/lib/Protocolpackage Protocol::HTTP2; use 5.008005; use strict; use warnings; our $VERSION = "1.10"; sub ident_plain { 'h2c'; } sub ident_tls { 'h2'; } 1; __END__ =encoding utf-8 =head1 NAME Protocol::HTTP2 - HTTP/2 protocol implementation (RFC 7540) =head1 SYNOPSIS use Protocol::HTTP2; # get protocol identification string for secure connections print Protocol::HTTP2::ident_tls; # h2 # get protocol identification string for non-secure connections print Protocol::HTTP2::ident_plain; # h2c =head1 DESCRIPTION Protocol::HTTP2 is HTTP/2 protocol implementation (L) with stateful decoders/encoders of HTTP/2 frames. You may use this module to implement your own HTTP/2 client/server/intermediate on top of your favorite event loop over plain or tls socket (see examples). =head1 STATUS Current status - beta. Structures, module names and methods seems like stable. I've started this project to understand internals of HTTP/2 and may be it will never become production, but at least it works. | Spec | status | | ----------------------- | --------------- | | Negotiation | ALPN, NPN, | | | Upgrade, direct | | Preface | + | | Headers (de)compression | + | | Stream states | + | | Flow control | ± | | Stream priority | ± | | Server push | + | | Connect method | - | | Frame | encoder | decoder | | --------------- |:-------:|:-------:| | DATA | ± | + | | HEADERS | + | + | | PRIORITY | + | + | | RST_STREAM | + | + | | SETTINGS | + | + | | PUSH_PROMISE | + | + | | PING | + | + | | GOAWAY | + | + | | WINDOW_UPDATE | + | + | | CONTINUATION | ± | + | =over =item - -- not implemeted =item ± -- incomplete =item + -- implemented (may even work) =back =head1 MODULES =head2 L Client protocol decoder/encoder with constructor of requests =head2 L Server protocol decoder/encoder with constructor of responses/pushes =head2 L Main low level module for protocol logic and state processing. Connection object is a mixin of L (frame encoding/decoding), L (stream operations) and L (HTTP/1.1 Upgrade support) =head2 L Module implements HPACK - Header Compression for HTTP/2 (L). =head2 L Module contains all defined in HTTP/2 protocol constants and default values =head2 L Module for debugging. You can setup HTTP2_DEBUG environment variable to change verbosity of the module (output to STDOUT). Default level is error. $ export HTTP2_DEBUG=debug $ perl ./http2_program =head1 SEE ALSO L - Protocol::HTTP2 wiki L - official HTTP/2 specification site L - http2 explained =head1 LICENSE Copyright (C) Vladimir Lettiev. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 AUTHOR Vladimir Lettiev Ethecrux@gmail.comE =cut Client.pm100644000764000764 3031013562447335 20326 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/lib/Protocol/HTTP2package Protocol::HTTP2::Client; use strict; use warnings; use Protocol::HTTP2::Connection; use Protocol::HTTP2::Constants qw(:frame_types :flags :states :endpoints :errors); use Protocol::HTTP2::Trace qw(tracer); use Carp; use Scalar::Util (); =encoding utf-8 =head1 NAME Protocol::HTTP2::Client - HTTP/2 client =head1 SYNOPSIS use Protocol::HTTP2::Client; # Create client object my $client = Protocol::HTTP2::Client->new; # Prepare first request $client->request( # HTTP/2 headers ':scheme' => 'http', ':authority' => 'localhost:8000', ':path' => '/', ':method' => 'GET', # HTTP/1.1 headers headers => [ 'accept' => '*/*', 'user-agent' => 'perl-Protocol-HTTP2/0.13', ], # Callback when receive server's response on_done => sub { my ( $headers, $data ) = @_; ... }, ); # Protocol::HTTP2 is just HTTP/2 protocol decoder/encoder # so you must create connection yourself use AnyEvent; use AnyEvent::Socket; use AnyEvent::Handle; my $w = AnyEvent->condvar; # Plain-text HTTP/2 connection tcp_connect 'localhost', 8000, sub { my ($fh) = @_ or die "connection failed: $!\n"; my $handle; $handle = AnyEvent::Handle->new( fh => $fh, autocork => 1, on_error => sub { $_[0]->destroy; print "connection error\n"; $w->send; }, on_eof => sub { $handle->destroy; $w->send; } ); # First write preface to peer while ( my $frame = $client->next_frame ) { $handle->push_write($frame); } # Receive servers frames # Reply to server $handle->on_read( sub { my $handle = shift; $client->feed( $handle->{rbuf} ); $handle->{rbuf} = undef; while ( my $frame = $client->next_frame ) { $handle->push_write($frame); } # Terminate connection if all done $handle->push_shutdown if $client->shutdown; } ); }; $w->recv; =head1 DESCRIPTION Protocol::HTTP2::Client is HTTP/2 client library. It's intended to make http2-client implementations on top of your favorite event-loop. =head2 METHODS =head3 new Initialize new client object my $client = Procotol::HTTP2::Client->new( %options ); Available options: =over =item on_push => sub {...} If server send push promise this callback will be invoked on_push => sub { # received PUSH PROMISE headers my $pp_header = shift; ... # if we want reject this push # return undef # if we want to accept pushed resource # return callback to receive data return sub { my ( $headers, $data ) = @_; ... } }, =item upgrade => 0|1 Use HTTP/1.1 Upgrade to upgrade protocol from HTTP/1.1 to HTTP/2. Upgrade possible only on plain (non-tls) connection. Default value is 0. See L =item keepalive => 0|1 Keep connection alive after requests. Default value is 0. Don't forget to explicitly call close method if set this to true. =item on_error => sub {...} Callback invoked on protocol errors on_error => sub { my $error = shift; ... }, =item on_change_state => sub {...} Callback invoked every time when http/2 streams change their state. See L on_change_state => sub { my ( $stream_id, $previous_state, $current_state ) = @_; ... }, =back =cut sub new { my ( $class, %opts ) = @_; my $self = { con => undef, input => '', active_streams => 0, keepalive => exists $opts{keepalive} ? delete $opts{keepalive} : 0, settings => exists $opts{settings} ? $opts{settings} : {}, }; if ( exists $opts{on_push} ) { Scalar::Util::weaken( my $self = $self ); my $cb = delete $opts{on_push}; $opts{on_new_peer_stream} = sub { my $stream_id = shift; my $pp_headers; $self->active_streams(+1); $self->{con}->stream_cb( $stream_id, RESERVED, sub { my $res = $cb->( $self->{con}->stream_pp_headers($stream_id) ); if ( $res && ref $cb eq 'CODE' ) { $self->{con}->stream_cb( $stream_id, CLOSED, sub { $res->( $self->{con}->stream_headers($stream_id), $self->{con}->stream_data($stream_id), ); $self->active_streams(-1); } ); } else { $self->{con} ->stream_error( $stream_id, REFUSED_STREAM ); $self->active_streams(-1); } } ); }; } $self->{con} = Protocol::HTTP2::Connection->new( CLIENT, %opts ); bless $self, $class; } sub active_streams { my $self = shift; my $add = shift || 0; $self->{active_streams} += $add; $self->{con}->finish unless $self->{active_streams} > 0 || $self->{keepalive}; } =head3 request Prepare HTTP/2 request. $client->request( # HTTP/2 headers ':scheme' => 'http', ':authority' => 'localhost:8000', ':path' => '/items', ':method' => 'POST', # HTTP/1.1 headers headers => [ 'content-type' => 'application/x-www-form-urlencoded', 'user-agent' => 'perl-Protocol-HTTP2/0.06', ], # Callback when receive server's response on_done => sub { my ( $headers, $data ) = @_; ... }, # Callback when receive stream reset on_error => sub { my $error_code = shift; }, # Body of POST request data => "hello=world&test=done", ); You can chaining request one by one: $client->request( 1-st request )->request( 2-nd request ); Available callbacks: =over =item on_done => sub {...} Invoked when full servers response is available on_done => sub { my ( $headers, $data ) = @_; ... }, =item on_headers => sub {...} Invoked as soon as headers have been successfully received from the server on_headers => sub { my $headers = shift; ... # if we want reject any data # return undef # continue return 1 } =item on_data => sub {...} If specified all data will be passed to this callback instead if on_done. on_done will receive empty string. on_data => sub { my ( $partial_data, $headers ) = @_; ... # if we want cancel download # return undef # continue downloading return 1 } =item on_error => sub {...} Callback invoked on stream errors on_error => sub { my $error = shift; ... } =back =cut my @must = (qw(:authority :method :path :scheme)); sub request { my ( $self, %h ) = @_; my @miss = grep { !exists $h{$_} } @must; croak "Missing fields in request: @miss" if @miss; my $con = $self->{con}; my $stream_id = $con->new_stream; unless ( defined $stream_id ) { if ( exists $con->{on_error} ) { $con->{on_error}->(PROTOCOL_ERROR); return $self; } else { croak "Can't create new stream, connection is closed"; } } $self->active_streams(+1); if ( $con->upgrade && !exists $self->{sent_upgrade} ) { $con->enqueue_raw( $con->upgrade_request( ( map { $_ => $h{$_} } @must ), headers => exists $h{headers} ? $h{headers} : [] ) ); $self->{sent_upgrade} = 1; $con->stream_state( $stream_id, HALF_CLOSED ); } else { if ( !$con->preface ) { $con->enqueue_raw( $con->preface_encode ), $con->enqueue( SETTINGS, 0, 0, $self->{settings} ); $con->preface(1); } $con->send_headers( $stream_id, [ ( map { $_ => $h{$_} } @must ), exists $h{headers} ? @{ $h{headers} } : () ], exists $h{data} ? 0 : 1 ); $con->send_data( $stream_id, $h{data}, 1 ) if exists $h{data}; } Scalar::Util::weaken $self; Scalar::Util::weaken $con; $con->stream_cb( $stream_id, CLOSED, sub { if ( exists $h{on_error} && $con->stream_reset($stream_id) ) { $h{on_error}->( $con->stream_reset($stream_id) ); } else { $h{on_done}->( $con->stream_headers($stream_id), $con->stream_data($stream_id), ); } $self->active_streams(-1); } ) if exists $h{on_done}; $con->stream_frame_cb( $stream_id, HEADERS, sub { my $res = $h{on_headers}->( $_[0] ); return if $res; $con->stream_error( $stream_id, REFUSED_STREAM ); } ) if exists $h{on_headers}; $con->stream_frame_cb( $stream_id, DATA, sub { my $res = $h{on_data}->( $_[0], $con->stream_headers($stream_id), ); return if $res; $con->stream_error( $stream_id, REFUSED_STREAM ); } ) if exists $h{on_data}; return $self; } =head3 keepalive Keep connection alive after requests my $bool = $client->keepalive; $client = $client->keepalive($bool); =cut sub keepalive { my $self = shift; return @_ ? scalar( $self->{keepalive} = shift, $self ) : $self->{keepalive}; } =head3 shutdown Get connection status: =over =item 0 - active =item 1 - closed (you can terminate connection) =back =cut sub shutdown { shift->{con}->shutdown; } =head3 close Explicitly close connection (send GOAWAY frame). This is required if client has keepalive option enabled. =cut sub close { shift->{con}->finish; } =head3 next_frame get next frame to send over connection to server. Returns: =over =item undef - on error =item 0 - nothing to send =item binary string - encoded frame =back # Example while ( my $frame = $client->next_frame ) { syswrite $fh, $frame; } =cut sub next_frame { my $self = shift; my $frame = $self->{con}->dequeue; tracer->debug("send one frame to wire\n") if $frame; return $frame; } =head3 feed Feed decoder with chunks of server's response sysread $fh, $binary_data, 4096; $client->feed($binary_data); =cut sub feed { my ( $self, $chunk ) = @_; $self->{input} .= $chunk; my $offset = 0; my $len; my $con = $self->{con}; tracer->debug( "got " . length($chunk) . " bytes on a wire\n" ); if ( $con->upgrade ) { $len = $con->decode_upgrade_response( \$self->{input}, $offset ); $con->shutdown(1) unless defined $len; return unless $len; $offset += $len; $con->upgrade(0); $con->enqueue_raw( $con->preface_encode ); $con->preface(1); } while ( $len = $con->frame_decode( \$self->{input}, $offset ) ) { tracer->debug("decoded frame at $offset, length $len\n"); $offset += $len; } substr( $self->{input}, 0, $offset ) = '' if $offset; } =head3 ping Send ping frame to server (to keep connection alive) $client->ping or $client->ping($payload); Payload can be arbitrary binary string and must contain 8 octets. If payload argument is omitted client will send random data. =cut sub ping { shift->{con}->send_ping(@_); } 1; Connection.pm100644000764000764 3377413562447335 21230 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/lib/Protocol/HTTP2package Protocol::HTTP2::Connection; use strict; use warnings; use Protocol::HTTP2::Constants qw(const_name :frame_types :errors :settings :flags :states :limits :endpoints); use Protocol::HTTP2::HeaderCompression qw(headers_encode); use Protocol::HTTP2::Frame; use Protocol::HTTP2::Stream; use Protocol::HTTP2::Upgrade; use Protocol::HTTP2::Trace qw(tracer); # Mixin our @ISA = qw(Protocol::HTTP2::Frame Protocol::HTTP2::Stream Protocol::HTTP2::Upgrade); # Default settings my %default_settings = ( &SETTINGS_HEADER_TABLE_SIZE => DEFAULT_HEADER_TABLE_SIZE, &SETTINGS_ENABLE_PUSH => DEFAULT_ENABLE_PUSH, &SETTINGS_MAX_CONCURRENT_STREAMS => DEFAULT_MAX_CONCURRENT_STREAMS, &SETTINGS_INITIAL_WINDOW_SIZE => DEFAULT_INITIAL_WINDOW_SIZE, &SETTINGS_MAX_FRAME_SIZE => DEFAULT_MAX_FRAME_SIZE, &SETTINGS_MAX_HEADER_LIST_SIZE => DEFAULT_MAX_HEADER_LIST_SIZE, ); sub new { my ( $class, $type, %opts ) = @_; my $self = bless { type => $type, streams => {}, last_stream => $type == CLIENT ? 1 : 2, last_peer_stream => 0, active_peer_streams => 0, encode_ctx => { # HPACK. Header Table header_table => [], # HPACK. Header Table size ht_size => 0, max_ht_size => DEFAULT_HEADER_TABLE_SIZE, settings => {%default_settings}, }, decode_ctx => { # HPACK. Header Table header_table => [], # HPACK. Header Table size ht_size => 0, max_ht_size => DEFAULT_HEADER_TABLE_SIZE, # HPACK. Emitted headers emitted_headers => [], # last frame frame => {}, settings => {%default_settings}, }, # Current error error => 0, # Output frames queue queue => [], # Connection must be shutdown shutdown => 0, # issued GOAWAY: no new streams on this connection goaway => 0, # get preface preface => 0, # perform upgrade upgrade => 0, # flow control fcw_send => DEFAULT_INITIAL_WINDOW_SIZE, fcw_recv => DEFAULT_INITIAL_WINDOW_SIZE, # stream where expected CONTINUATION frames pending_stream => undef, }, $class; for (qw(on_change_state on_new_peer_stream on_error upgrade)) { $self->{$_} = $opts{$_} if exists $opts{$_}; } if ( exists $opts{settings} ) { for ( keys %{ $opts{settings} } ) { $self->{decode_ctx}->{settings}->{$_} = $opts{settings}{$_}; } } # Sync decode context max_ht_size $self->{decode_ctx}->{max_ht_size} = $self->{decode_ctx}->{settings}->{&SETTINGS_HEADER_TABLE_SIZE}; $self; } sub decode_context { shift->{decode_ctx}; } sub encode_context { shift->{encode_ctx}; } sub pending_stream { shift->{pending_stream}; } sub dequeue { my $self = shift; shift @{ $self->{queue} }; } sub enqueue_raw { my $self = shift; push @{ $self->{queue} }, @_; } sub enqueue { my $self = shift; while ( my ( $type, $flags, $stream_id, $data_ref ) = splice( @_, 0, 4 ) ) { push @{ $self->{queue} }, $self->frame_encode( $type, $flags, $stream_id, $data_ref ); $self->state_machine( 'send', $type, $flags, $stream_id ); } } sub enqueue_first { my $self = shift; my $i = 0; for ( 0 .. $#{ $self->{queue} } ) { my $type = ( $self->frame_header_decode( \$self->{queue}->[$_], 0 ) )[1]; last if $type != CONTINUATION && $type != PING; $i++; } while ( my ( $type, $flags, $stream_id, $data_ref ) = splice( @_, 0, 4 ) ) { splice @{ $self->{queue} }, $i++, 0, $self->frame_encode( $type, $flags, $stream_id, $data_ref ); $self->state_machine( 'send', $type, $flags, $stream_id ); } } sub finish { my $self = shift; $self->enqueue( GOAWAY, 0, 0, [ $self->{last_peer_stream}, $self->{error} ] ) unless $self->shutdown; $self->shutdown(1); } sub shutdown { my $self = shift; $self->{shutdown} = shift if @_; $self->{shutdown}; } sub goaway { my $self = shift; $self->{goaway} = shift if @_; $self->{goaway}; } sub preface { my $self = shift; $self->{preface} = shift if @_; $self->{preface}; } sub upgrade { my $self = shift; $self->{upgrade} = shift if @_; $self->{upgrade}; } sub state_machine { my ( $self, $act, $type, $flags, $stream_id ) = @_; return if $stream_id == 0 || $type == SETTINGS || $type == GOAWAY || $self->upgrade || !$self->preface; my $promised_sid = $self->stream_promised_sid($stream_id); my $prev_state = $self->{streams}->{ $promised_sid || $stream_id }->{state}; # REFUSED_STREAM error return if !defined $prev_state && $type == RST_STREAM && $act eq 'send'; # Direction server->client my $srv2cln = ( $self->{type} == SERVER && $act eq 'send' ) || ( $self->{type} == CLIENT && $act eq 'recv' ); # Direction client->server my $cln2srv = ( $self->{type} == SERVER && $act eq 'recv' ) || ( $self->{type} == CLIENT && $act eq 'send' ); # Do we expect CONTINUATION after this frame? my $pending = ( $type == HEADERS || $type == PUSH_PROMISE ) && !( $flags & END_HEADERS ); #tracer->debug( # sprintf "\e[0;31mStream state: frame %s is %s%s on %s stream %i\e[m\n", # const_name( "frame_types", $type ), # $act, # $pending ? "*" : "", # const_name( "states", $prev_state ), # $promised_sid || $stream_id, #); # Wait until all CONTINUATION frames arrive if ( my $ps = $self->stream_pending_state($stream_id) ) { if ( $type != CONTINUATION ) { tracer->error( sprintf "invalid frame type %s. Expected CONTINUATION frame\n", const_name( "frame_types", $type ) ); $self->error(PROTOCOL_ERROR); } elsif ( $flags & END_HEADERS ) { $self->stream_promised_sid( $stream_id, undef ) if $promised_sid; $self->stream_pending_state( $promised_sid || $stream_id, undef ); $self->stream_state( $promised_sid || $stream_id, $ps ); } } # Unexpected CONTINUATION frame elsif ( $type == CONTINUATION ) { tracer->error("Unexpected CONTINUATION frame\n"); $self->error(PROTOCOL_ERROR); } # State machine # IDLE elsif ( $prev_state == IDLE ) { if ( $type == HEADERS && $cln2srv ) { $self->stream_state( $stream_id, ( $flags & END_STREAM ) ? HALF_CLOSED : OPEN, $pending ); } elsif ( $type == PUSH_PROMISE && $srv2cln ) { $self->stream_state( $promised_sid, RESERVED, $pending ); $self->stream_promised_sid( $stream_id, undef ) if $flags & END_HEADERS; } # first frame in stream is invalid, so state is yet IDLE elsif ( $type == RST_STREAM && $act eq 'send' ) { tracer->notice('send RST_STREAM on IDLE state. possible bug?'); $self->stream_state( $stream_id, CLOSED ); } elsif ( $type != PRIORITY ) { tracer->error( sprintf "invalid frame type %s for current stream state %s\n", const_name( "frame_types", $type ), const_name( "states", $prev_state ) ); $self->error(PROTOCOL_ERROR); } } # OPEN elsif ( $prev_state == OPEN ) { if ( ( $flags & END_STREAM ) && ( $type == DATA || $type == HEADERS ) ) { $self->stream_state( $stream_id, HALF_CLOSED, $pending ); } elsif ( $type == RST_STREAM ) { $self->stream_state( $stream_id, CLOSED ); } elsif ($type == HEADERS && !$pending && $self->stream_trailer($stream_id) ) { tracer->error("expected END_STREAM flag for trailer HEADERS frame"); $self->error(PROTOCOL_ERROR); } } # RESERVED (local/remote) elsif ( $prev_state == RESERVED ) { if ( $type == RST_STREAM ) { $self->stream_state( $stream_id, CLOSED ); } elsif ( $type == HEADERS && $srv2cln ) { $self->stream_state( $stream_id, ( $flags & END_STREAM ) ? CLOSED : HALF_CLOSED, $pending ); } elsif ( $type != PRIORITY && $cln2srv ) { tracer->error("invalid frame $type for state RESERVED"); $self->error(PROTOCOL_ERROR); } } # HALF_CLOSED (local/remote) elsif ( $prev_state == HALF_CLOSED ) { if ( ( $type == RST_STREAM ) || ( ( $flags & END_STREAM ) && $srv2cln ) ) { $self->stream_state( $stream_id, CLOSED, $pending ); } elsif ( ( !grep { $type == $_ } ( WINDOW_UPDATE, PRIORITY ) ) && $cln2srv ) { tracer->error( sprintf "invalid frame %s for state HALF CLOSED\n", const_name( "frame_types", $type ) ); $self->error(PROTOCOL_ERROR); } } # CLOSED elsif ( $prev_state == CLOSED ) { if ( $type != PRIORITY && ( $type != WINDOW_UPDATE && $cln2srv ) ) { tracer->error("stream is closed\n"); $self->error(STREAM_CLOSED); } } else { tracer->error("oops!\n"); $self->error(INTERNAL_ERROR); } } # TODO: move this to some other module sub send_headers { my ( $self, $stream_id, $headers, $end ) = @_; my $max_size = $self->enc_setting(SETTINGS_MAX_FRAME_SIZE); my $header_block = headers_encode( $self->encode_context, $headers ); my $flags = $end ? END_STREAM : 0; $flags |= END_HEADERS if length($header_block) <= $max_size; $self->enqueue( HEADERS, $flags, $stream_id, { hblock => \substr( $header_block, 0, $max_size, '' ) } ); while ( length($header_block) > 0 ) { my $flags = length($header_block) <= $max_size ? 0 : END_HEADERS; $self->enqueue( CONTINUATION, $flags, $stream_id, \substr( $header_block, 0, $max_size, '' ) ); } } sub send_pp_headers { my ( $self, $stream_id, $promised_id, $headers ) = @_; my $max_size = $self->enc_setting(SETTINGS_MAX_FRAME_SIZE); my $header_block = headers_encode( $self->encode_context, $headers ); my $flags = length($header_block) <= $max_size ? END_HEADERS : 0; $self->enqueue( PUSH_PROMISE, $flags, $stream_id, [ $promised_id, \substr( $header_block, 0, $max_size - 4, '' ) ] ); while ( length($header_block) > 0 ) { my $flags = length($header_block) <= $max_size ? 0 : END_HEADERS; $self->enqueue( CONTINUATION, $flags, $stream_id, \substr( $header_block, 0, $max_size, '' ) ); } } sub send_data { my ( $self, $stream_id, $chunk, $end ) = @_; my $data = $self->stream_blocked_data($stream_id); $data .= defined $chunk ? $chunk : ''; $self->stream_end( $stream_id, $end ) if defined $end; $end = $self->stream_end($stream_id); while (1) { my $l = length($data); my $size = $self->enc_setting(SETTINGS_MAX_FRAME_SIZE); for ( $l, $self->fcw_send, $self->stream_fcw_send($stream_id) ) { $size = $_ if $size > $_; } # Flow control last if $l != 0 && $size <= 0; $self->fcw_send( -$size ); $self->stream_fcw_send( $stream_id, -$size ); $self->enqueue( DATA, $end && $l == $size ? END_STREAM : 0, $stream_id, \substr( $data, 0, $size, '' ) ); last if $l == $size; } $self->stream_blocked_data( $stream_id, $data ); } sub send_blocked { my $self = shift; for my $stream_id ( keys %{ $self->{streams} } ) { $self->stream_send_blocked($stream_id); } } sub error { my $self = shift; if ( @_ && !$self->{shutdown} ) { $self->{error} = shift; $self->{on_error}->( $self->{error} ) if exists $self->{on_error}; $self->finish; } $self->{error}; } sub setting { require Carp; Carp::confess("setting is deprecated\n"); } sub _setting { my ( $ctx, $self, $setting ) = @_; my $s = $self->{$ctx}->{settings}; return undef unless exists $s->{$setting}; $s->{$setting} = pop if @_ > 3; $s->{$setting}; } sub enc_setting { _setting( 'encode_ctx', @_ ); } sub dec_setting { _setting( 'decode_ctx', @_ ); } sub accept_settings { my $self = shift; $self->enqueue( SETTINGS, ACK, 0, {} ); } # Flow control windown of connection sub _fcw { my $dir = shift; my $self = shift; if (@_) { $self->{$dir} += shift; tracer->debug( "$dir now is " . $self->{$dir} . "\n" ); } $self->{$dir}; } sub fcw_send { _fcw( 'fcw_send', @_ ); } sub fcw_recv { _fcw( 'fcw_recv', @_ ); } sub fcw_update { my $self = shift; # TODO: check size of data in memory my $size = $self->dec_setting(SETTINGS_INITIAL_WINDOW_SIZE); tracer->debug("update fcw recv of connection with $size b.\n"); $self->fcw_recv($size); $self->enqueue( WINDOW_UPDATE, 0, 0, $size ); } sub fcw_initial_change { my ( $self, $size ) = @_; my $prev_size = $self->enc_setting(SETTINGS_INITIAL_WINDOW_SIZE); my $diff = $size - $prev_size; tracer->debug( "Change flow control window on not closed streams with diff $diff\n"); for my $stream_id ( keys %{ $self->{streams} } ) { next if $self->stream_state($stream_id) == CLOSED; $self->stream_fcw_send( $stream_id, $diff ); } } sub ack_ping { my ( $self, $payload_ref ) = @_; $self->enqueue_first( PING, ACK, 0, $payload_ref ); } sub send_ping { my ( $self, $payload ) = @_; if ( !defined $payload ) { $payload = pack "C*", map { rand(256) } 1 .. PING_PAYLOAD_SIZE; } elsif ( length($payload) != PING_PAYLOAD_SIZE ) { $payload = sprintf "%*.*s", -PING_PAYLOAD_SIZE(), PING_PAYLOAD_SIZE, $payload; } $self->enqueue( PING, 0, 0, \$payload ); } 1; Constants.pm100644000764000764 710313562447335 21050 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/lib/Protocol/HTTP2package Protocol::HTTP2::Constants; use strict; use warnings; use constant { # Header Compression MAX_INT_SIZE => 4, MAX_PAYLOAD_SIZE => ( 1 << 24 ) - 1, # Frame FRAME_HEADER_SIZE => 9, # Flow control MAX_FCW_SIZE => ( 1 << 31 ) - 1, # Ping payload PING_PAYLOAD_SIZE => 8, # Settings defaults DEFAULT_HEADER_TABLE_SIZE => 4_096, DEFAULT_ENABLE_PUSH => 1, DEFAULT_MAX_CONCURRENT_STREAMS => 100, DEFAULT_INITIAL_WINDOW_SIZE => 65_535, DEFAULT_MAX_FRAME_SIZE => 16_384, DEFAULT_MAX_HEADER_LIST_SIZE => 65_536, # Priority DEFAULT_WEIGHT => 16, # Stream states IDLE => 1, RESERVED => 2, OPEN => 3, HALF_CLOSED => 4, CLOSED => 5, # Endpoint types CLIENT => 1, SERVER => 2, # Preface string PREFACE => "PRI * HTTP/2.0\x0d\x0a\x0d\x0aSM\x0d\x0a\x0d\x0a", # Frame types DATA => 0, HEADERS => 1, PRIORITY => 2, RST_STREAM => 3, SETTINGS => 4, PUSH_PROMISE => 5, PING => 6, GOAWAY => 7, WINDOW_UPDATE => 8, CONTINUATION => 9, # Flags ACK => 0x1, END_STREAM => 0x1, END_HEADERS => 0x4, PADDED => 0x8, PRIORITY_FLAG => 0x20, # Errors NO_ERROR => 0, PROTOCOL_ERROR => 1, INTERNAL_ERROR => 2, FLOW_CONTROL_ERROR => 3, SETTINGS_TIMEOUT => 4, STREAM_CLOSED => 5, FRAME_SIZE_ERROR => 6, REFUSED_STREAM => 7, CANCEL => 8, COMPRESSION_ERROR => 9, CONNECT_ERROR => 10, ENHANCE_YOUR_CALM => 11, INADEQUATE_SECURITY => 12, HTTP_1_1_REQUIRED => 13, # SETTINGS SETTINGS_HEADER_TABLE_SIZE => 1, SETTINGS_ENABLE_PUSH => 2, SETTINGS_MAX_CONCURRENT_STREAMS => 3, SETTINGS_INITIAL_WINDOW_SIZE => 4, SETTINGS_MAX_FRAME_SIZE => 5, SETTINGS_MAX_HEADER_LIST_SIZE => 6, }; require Exporter; our @ISA = qw(Exporter); our %EXPORT_TAGS = ( frame_types => [ qw(DATA HEADERS PRIORITY RST_STREAM SETTINGS PUSH_PROMISE PING GOAWAY WINDOW_UPDATE CONTINUATION) ], errors => [ qw(NO_ERROR PROTOCOL_ERROR INTERNAL_ERROR FLOW_CONTROL_ERROR SETTINGS_TIMEOUT STREAM_CLOSED FRAME_SIZE_ERROR REFUSED_STREAM CANCEL COMPRESSION_ERROR CONNECT_ERROR ENHANCE_YOUR_CALM INADEQUATE_SECURITY HTTP_1_1_REQUIRED) ], preface => [qw(PREFACE)], flags => [qw(ACK END_STREAM END_HEADERS PADDED PRIORITY_FLAG)], settings => [ qw(SETTINGS_HEADER_TABLE_SIZE SETTINGS_ENABLE_PUSH SETTINGS_MAX_CONCURRENT_STREAMS SETTINGS_INITIAL_WINDOW_SIZE SETTINGS_MAX_FRAME_SIZE SETTINGS_MAX_HEADER_LIST_SIZE) ], limits => [ qw(MAX_INT_SIZE MAX_PAYLOAD_SIZE PING_PAYLOAD_SIZE MAX_FCW_SIZE DEFAULT_WEIGHT DEFAULT_HEADER_TABLE_SIZE DEFAULT_MAX_CONCURRENT_STREAMS DEFAULT_ENABLE_PUSH DEFAULT_INITIAL_WINDOW_SIZE DEFAULT_MAX_FRAME_SIZE DEFAULT_MAX_HEADER_LIST_SIZE FRAME_HEADER_SIZE) ], states => [qw(IDLE RESERVED OPEN HALF_CLOSED CLOSED)], endpoints => [qw(CLIENT SERVER)], ); my %reverse; { no strict 'refs'; for my $k ( keys %EXPORT_TAGS ) { for my $v ( @{ $EXPORT_TAGS{$k} } ) { $reverse{$k}{ &{$v} } = $v; } } } sub const_name { my ( $tag, $value ) = @_; exists $reverse{$tag} ? ( $reverse{$tag}{$value} || '' ) : ''; } our @EXPORT_OK = ( qw(const_name), map { @$_ } values %EXPORT_TAGS ); 1; Frame.pm100644000764000764 1221413562447335 20145 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/lib/Protocol/HTTP2package Protocol::HTTP2::Frame; use strict; use warnings; use Protocol::HTTP2::Trace qw(tracer); use Protocol::HTTP2::Constants qw(const_name :frame_types :errors :preface :states :flags :limits :settings); use Protocol::HTTP2::Frame::Data; use Protocol::HTTP2::Frame::Headers; use Protocol::HTTP2::Frame::Priority; use Protocol::HTTP2::Frame::Rst_stream; use Protocol::HTTP2::Frame::Settings; use Protocol::HTTP2::Frame::Push_promise; use Protocol::HTTP2::Frame::Ping; use Protocol::HTTP2::Frame::Goaway; use Protocol::HTTP2::Frame::Window_update; use Protocol::HTTP2::Frame::Continuation; # Table of payload decoders my %frame_class = ( &DATA => 'Data', &HEADERS => 'Headers', &PRIORITY => 'Priority', &RST_STREAM => 'Rst_stream', &SETTINGS => 'Settings', &PUSH_PROMISE => 'Push_promise', &PING => 'Ping', &GOAWAY => 'Goaway', &WINDOW_UPDATE => 'Window_update', &CONTINUATION => 'Continuation', ); my %decoder = map { $_ => \&{ 'Protocol::HTTP2::Frame::' . $frame_class{$_} . '::decode' } } keys %frame_class; my %encoder = map { $_ => \&{ 'Protocol::HTTP2::Frame::' . $frame_class{$_} . '::encode' } } keys %frame_class; sub frame_encode { my ( $con, $type, $flags, $stream_id, $data_ref ) = @_; my $payload = $encoder{$type}->( $con, \$flags, $stream_id, $data_ref ); my $l = length $payload; pack( 'CnC2N', ( $l >> 16 ), ( $l & 0xFFFF ), $type, $flags, $stream_id ) . $payload; } sub preface_decode { my ( $con, $buf_ref, $buf_offset ) = @_; return 0 if length($$buf_ref) - $buf_offset < length(PREFACE); return index( $$buf_ref, PREFACE, $buf_offset ) == -1 ? undef : length(PREFACE); } sub preface_encode { PREFACE; } sub frame_header_decode { my ( undef, $buf_ref, $buf_offset ) = @_; my ( $hl, $ll, $type, $flags, $stream_id ) = unpack( 'CnC2N', substr( $$buf_ref, $buf_offset, FRAME_HEADER_SIZE ) ); my $length = ( $hl << 16 ) + $ll; $stream_id &= 0x7FFF_FFFF; return $length, $type, $flags, $stream_id; } sub frame_decode { my ( $con, $buf_ref, $buf_offset ) = @_; return 0 if length($$buf_ref) - $buf_offset < FRAME_HEADER_SIZE; my ( $length, $type, $flags, $stream_id ) = $con->frame_header_decode( $buf_ref, $buf_offset ); if ( $length > $con->dec_setting(SETTINGS_MAX_FRAME_SIZE) ) { tracer->error("Frame is too large: $length\n"); $con->error(FRAME_SIZE_ERROR); return undef; } return 0 if length($$buf_ref) - $buf_offset - FRAME_HEADER_SIZE - $length < 0; tracer->debug( sprintf "TYPE = %s(%i), FLAGS = %08b, STREAM_ID = %i, " . "LENGTH = %i\n", exists $frame_class{$type} ? const_name( "frame_types", $type ) : "UNKNOWN", $type, $flags, $stream_id, $length ); my $pending_stream_id = $con->pending_stream; if ( $pending_stream_id && ( $type != CONTINUATION || $pending_stream_id != $stream_id ) ) { tracer->debug("Expected CONTINUATION for stream $pending_stream_id"); $con->error(PROTOCOL_ERROR); return undef; } # Unknown type of frame if ( !exists $frame_class{$type} ) { tracer->info("ignore unknown frame type $type"); return FRAME_HEADER_SIZE + $length; } $con->decode_context->{frame} = { type => $type, flags => $flags, length => $length, stream => $stream_id, }; # Try to create new stream structure if ( $stream_id && !$con->stream($stream_id) && !$con->new_peer_stream($stream_id) ) { return $con->error ? undef : FRAME_HEADER_SIZE + $length; } return undef unless defined $decoder{$type} ->( $con, $buf_ref, $buf_offset + FRAME_HEADER_SIZE, $length ); # Arrived frame may change state of stream $con->state_machine( 'recv', $type, $flags, $stream_id ); return FRAME_HEADER_SIZE + $length; } =pod =head1 NOTES =head2 Frame Types vs Flags and Stream ID Table represent possible combination of frame types and flags. Last column -- Stream ID of frame types (x -- sid >= 1, 0 -- sid = 0) +-END_STREAM 0x1 | +-ACK 0x1 | | +-END_HEADERS 0x4 | | | +-PADDED 0x8 | | | | +-PRIORITY 0x20 | | | | | +-stream id (value) | | | | | | | frame type\flag | V | V | V | V | V | | V | | --------------- |:-:|:-:|:-:|:-:|:-:| - |:---:| | DATA | x | | | x | | | x | | HEADERS | x | | x | x | x | | x | | PRIORITY | | | | | | | x | | RST_STREAM | | | | | | | x | | SETTINGS | | x | | | | | 0 | | PUSH_PROMISE | | | x | x | | | x | | PING | | x | | | | | 0 | | GOAWAY | | | | | | | 0 | | WINDOW_UPDATE | | | | | | | 0/x | | CONTINUATION | | | x | x | | | x | =cut 1; Continuation.pm100644000764000764 155113562447335 22601 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/lib/Protocol/HTTP2/Framepackage Protocol::HTTP2::Frame::Continuation; use strict; use warnings; use Protocol::HTTP2::Constants qw(:flags :errors); use Protocol::HTTP2::Trace qw(tracer); sub decode { my ( $con, $buf_ref, $buf_offset, $length ) = @_; my $frame_ref = $con->decode_context->{frame}; # Protocol errors if ( # CONTINUATION frames MUST be associated with a stream $frame_ref->{stream} == 0 ) { $con->error(PROTOCOL_ERROR); return undef; } $con->stream_header_block( $frame_ref->{stream}, substr( $$buf_ref, $buf_offset, $length ) ); # Stream header block complete $con->stream_headers_done( $frame_ref->{stream} ) or return undef if $frame_ref->{flags} & END_HEADERS; return $length; } sub encode { my ( $con, $flags_ref, $stream, $data_ref ) = @_; return $$data_ref; } 1; Data.pm100644000764000764 443213562447335 21001 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/lib/Protocol/HTTP2/Framepackage Protocol::HTTP2::Frame::Data; use strict; use warnings; use Protocol::HTTP2::Constants qw(:flags :errors :settings :limits); use Protocol::HTTP2::Trace qw(tracer); sub decode { my ( $con, $buf_ref, $buf_offset, $length ) = @_; my ( $pad, $offset ) = ( 0, 0 ); my $frame_ref = $con->decode_context->{frame}; # Protocol errors if ( # DATA frames MUST be associated with a stream $frame_ref->{stream} == 0 ) { $con->error(PROTOCOL_ERROR); return undef; } if ( $frame_ref->{flags} & PADDED ) { $pad = unpack( 'C', substr( $$buf_ref, $buf_offset ) ); $offset += 1; } my $dblock_size = $length - $offset - $pad; if ( $dblock_size < 0 ) { tracer->error("Not enough space for data block\n"); $con->error(PROTOCOL_ERROR); return undef; } my $fcw = $con->fcw_recv( -$length ); my $stream_fcw = $con->stream_fcw_recv( $frame_ref->{stream}, -$length ); if ( $fcw < 0 || $stream_fcw < 0 ) { tracer->warning( "received data overflow flow control window: $fcw|$stream_fcw\n"); $con->stream_error( $frame_ref->{stream}, FLOW_CONTROL_ERROR ); return $length; } $con->fcw_update() if $fcw < $con->dec_setting(SETTINGS_MAX_FRAME_SIZE); $con->stream_fcw_update( $frame_ref->{stream} ) if $stream_fcw < $con->dec_setting(SETTINGS_MAX_FRAME_SIZE) && !( $frame_ref->{flags} & END_STREAM ); return $length unless $dblock_size; my $data = substr $$buf_ref, $buf_offset + $offset, $dblock_size; # Update stream data container $con->stream_data( $frame_ref->{stream}, $data ); # Check length of data matched content-length in header if ( $frame_ref->{flags} & END_STREAM ) { my $slen = $con->stream_length( $frame_ref->{stream} ); if ( defined $slen && defined $con->stream_data( $frame_ref->{stream} ) && $slen != length $con->stream_data( $frame_ref->{stream} ) ) { tracer->warning( "content-length header don't match data frames size\n"); $con->stream_error( $frame_ref->{stream}, PROTOCOL_ERROR ); } } return $length; } sub encode { my ( $con, $flags_ref, $stream_id, $data_ref ) = @_; return $$data_ref; } 1; Goaway.pm100644000764000764 227013562447335 21355 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/lib/Protocol/HTTP2/Framepackage Protocol::HTTP2::Frame::Goaway; use strict; use warnings; use Protocol::HTTP2::Constants qw(const_name :flags :errors); use Protocol::HTTP2::Trace qw(tracer bin2hex); sub decode { my ( $con, $buf_ref, $buf_offset, $length ) = @_; my $frame_ref = $con->decode_context->{frame}; if ( $frame_ref->{stream} != 0 ) { $con->error(PROTOCOL_ERROR); return undef; } my ( $last_stream_id, $error_code ) = unpack( 'N2', substr( $$buf_ref, $buf_offset, 8 ) ); $last_stream_id &= 0x7FFF_FFFF; tracer->debug( "GOAWAY with error code " . const_name( 'errors', $error_code ) . " last stream is $last_stream_id\n" ); tracer->debug( "additional debug data: " . bin2hex( substr( $$buf_ref, $buf_offset + 8 ) ) . "\n" ) if $length - 8 > 0; $con->goaway(1); return $length; } sub encode { my ( $con, $flags_ref, $stream, $data ) = @_; $con->goaway(1); my $payload = pack( 'N2', @$data ); tracer->debug( "\tGOAWAY: last stream = $data->[0], error = " . const_name( "errors", $data->[1] ) . "\n" ); $payload .= $data->[2] if @$data > 2; return $payload; } 1; Headers.pm100644000764000764 462513562447335 21507 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/lib/Protocol/HTTP2/Framepackage Protocol::HTTP2::Frame::Headers; use strict; use warnings; use Protocol::HTTP2::Constants qw(:flags :errors :states :limits); use Protocol::HTTP2::Trace qw(tracer); # 6.2 HEADERS sub decode { my ( $con, $buf_ref, $buf_offset, $length ) = @_; my ( $pad, $offset, $weight, $exclusive, $stream_dep ) = ( 0, 0 ); my $frame_ref = $con->decode_context->{frame}; # Protocol errors if ( # HEADERS frames MUST be associated with a stream $frame_ref->{stream} == 0 ) { $con->error(PROTOCOL_ERROR); return undef; } if ( $frame_ref->{flags} & PADDED ) { $pad = unpack( 'C', substr( $$buf_ref, $buf_offset, 1 ) ); $offset += 1; } if ( $frame_ref->{flags} & PRIORITY_FLAG ) { ( $stream_dep, $weight ) = unpack( 'NC', substr( $$buf_ref, $buf_offset + $offset, 5 ) ); $exclusive = $stream_dep >> 31; $stream_dep &= 0x7FFF_FFFF; $weight++; $con->stream_weight( $frame_ref->{stream}, $weight ); unless ( $con->stream_reprio( $frame_ref->{stream}, $exclusive, $stream_dep ) ) { tracer->error("Malformed HEADERS frame priority"); $con->error(PROTOCOL_ERROR); return undef; } $offset += 5; } # Not enough space for header block my $hblock_size = $length - $offset - $pad; if ( $hblock_size < 0 ) { $con->error(PROTOCOL_ERROR); return undef; } $con->stream_header_block( $frame_ref->{stream}, substr( $$buf_ref, $buf_offset + $offset, $hblock_size ) ); # Stream header block complete $con->stream_headers_done( $frame_ref->{stream} ) or return undef if $frame_ref->{flags} & END_HEADERS; return $length; } sub encode { my ( $con, $flags_ref, $stream, $data_ref ) = @_; my $res = ''; if ( exists $data_ref->{padding} ) { $$flags_ref |= PADDED; $res .= pack 'C', $data_ref->{padding}; } if ( exists $data_ref->{stream_dep} || exists $data_ref->{weight} ) { $$flags_ref |= PRIORITY_FLAG; my $weight = ( $data_ref->{weight} || DEFAULT_WEIGHT ) - 1; my $stream_dep = $data_ref->{stream_dep} || 0; $stream_dep |= ( 1 << 31 ) if $data_ref->{exclusive}; $res .= pack 'NC', $stream_dep, $weight; } return $res . ${ $data_ref->{hblock} }; } 1; Ping.pm100644000764000764 160513562447335 21024 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/lib/Protocol/HTTP2/Framepackage Protocol::HTTP2::Frame::Ping; use strict; use warnings; use Protocol::HTTP2::Constants qw(:flags :errors :limits); use Protocol::HTTP2::Trace qw(tracer); sub decode { my ( $con, $buf_ref, $buf_offset, $length ) = @_; my $frame_ref = $con->decode_context->{frame}; # PING associated with connection if ( $frame_ref->{stream} != 0 ) { $con->error(PROTOCOL_ERROR); return undef; } # payload is 8 octets if ( $length != PING_PAYLOAD_SIZE ) { $con->error(FRAME_SIZE_ERROR); return undef; } $con->ack_ping( \substr $$buf_ref, $buf_offset, $length ) unless $frame_ref->{flags} & ACK; return $length; } sub encode { my ( $con, $flags_ref, $stream, $data_ref ) = @_; if ( length($$data_ref) != PING_PAYLOAD_SIZE ) { $con->error(INTERNAL_ERROR); return undef; } return $$data_ref; } 1; Priority.pm100644000764000764 225713562447335 21754 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/lib/Protocol/HTTP2/Framepackage Protocol::HTTP2::Frame::Priority; use strict; use warnings; use Protocol::HTTP2::Constants qw(:flags :errors); use Protocol::HTTP2::Trace qw(tracer); sub decode { my ( $con, $buf_ref, $buf_offset, $length ) = @_; my $frame_ref = $con->decode_context->{frame}; # Priority frames MUST be associated with a stream if ( $frame_ref->{stream} == 0 ) { $con->error(PROTOCOL_ERROR); return undef; } if ( $length != 5 ) { $con->error(FRAME_SIZE_ERROR); return undef; } my ( $stream_dep, $weight ) = unpack( 'NC', substr( $$buf_ref, $buf_offset, 5 ) ); my $exclusive = $stream_dep >> 31; $stream_dep &= 0x7FFF_FFFF; $weight++; $con->stream_weight( $frame_ref->{stream}, $weight ); unless ( $con->stream_reprio( $frame_ref->{stream}, $exclusive, $stream_dep ) ) { tracer->error("Malformed priority frame"); $con->error(PROTOCOL_ERROR); return undef; } return $length; } sub encode { my ( $con, $flags_ref, $stream, $data_ref ) = @_; my $stream_dep = $data_ref->[0]; my $weight = $data_ref->[1] - 1; pack( 'NC', $stream_dep, $weight ); } 1; Push_promise.pm100644000764000764 327413562447335 22610 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/lib/Protocol/HTTP2/Framepackage Protocol::HTTP2::Frame::Push_promise; use strict; use warnings; use Protocol::HTTP2::Constants qw(:flags :errors :settings); use Protocol::HTTP2::Trace qw(tracer); sub decode { my ( $con, $buf_ref, $buf_offset, $length ) = @_; my ( $pad, $offset ) = ( 0, 0 ); my $frame_ref = $con->decode_context->{frame}; # Protocol errors if ( # PP frames MUST be associated with a stream $frame_ref->{stream} == 0 # PP frames MUST be allowed || !$con->dec_setting(SETTINGS_ENABLE_PUSH) ) { $con->error(PROTOCOL_ERROR); return undef; } if ( $frame_ref->{flags} & PADDED ) { $pad = unpack( 'C', substr( $$buf_ref, $buf_offset ) ); $offset += 1; } my $promised_sid = unpack 'N', substr $$buf_ref, $buf_offset + $offset, 4; $promised_sid &= 0x7FFF_FFFF; $offset += 4; my $hblock_size = $length - $offset - $pad; if ( $hblock_size < 0 ) { tracer->error("Not enough space for header block\n"); $con->error(FRAME_SIZE_ERROR); return undef; } $con->new_peer_stream($promised_sid) or return undef; $con->stream_promised_sid( $frame_ref->{stream}, $promised_sid ); $con->stream_header_block( $frame_ref->{stream}, substr( $$buf_ref, $buf_offset + $offset, $hblock_size ) ); # PP header block complete $con->stream_headers_done( $frame_ref->{stream} ) or return undef if $frame_ref->{flags} & END_HEADERS; return $length; } sub encode { my ( $con, $flags_ref, $stream_id, $data_ref ) = @_; my $promised_id = $data_ref->[0]; my $hblock_ref = $data_ref->[1]; return pack( 'N', $promised_id ) . $$hblock_ref; } 1; Rst_stream.pm100644000764000764 206613562447335 22254 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/lib/Protocol/HTTP2/Framepackage Protocol::HTTP2::Frame::Rst_stream; use strict; use warnings; use Protocol::HTTP2::Constants qw(const_name :flags :errors); use Protocol::HTTP2::Trace qw(tracer); sub decode { my ( $con, $buf_ref, $buf_offset, $length ) = @_; my $frame_ref = $con->decode_context->{frame}; # RST_STREAM associated with stream if ( $frame_ref->{stream} == 0 ) { tracer->error("Received reset stream with stream id 0"); $con->error(PROTOCOL_ERROR); return undef; } if ( $length != 4 ) { tracer->error("Received reset stream with invalid length $length"); $con->error(FRAME_SIZE_ERROR); return undef; } my $code = unpack( 'N', substr( $$buf_ref, $buf_offset, 4 ) ); tracer->debug( "Receive reset stream with error code " . const_name( "errors", $code ) . "\n" ); $con->stream_reset( $frame_ref->{stream}, $code ); return $length; } sub encode { my ( $con, $flags_ref, $stream, $data ) = @_; $con->stream_reset( $stream, $data ); return pack 'N', $data; } 1; Settings.pm100644000764000764 566513562447335 21741 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/lib/Protocol/HTTP2/Framepackage Protocol::HTTP2::Frame::Settings; use strict; use warnings; use Protocol::HTTP2::Constants qw(const_name :flags :errors :limits :settings); use Protocol::HTTP2::Trace qw(tracer); my %s_check = ( &SETTINGS_MAX_FRAME_SIZE => { validator => sub { $_[0] <= MAX_PAYLOAD_SIZE && $_[0] >= DEFAULT_MAX_FRAME_SIZE; }, error => PROTOCOL_ERROR }, &SETTINGS_ENABLE_PUSH => { validator => sub { $_[0] == 0 || $_[0] == 1; }, error => PROTOCOL_ERROR }, &SETTINGS_INITIAL_WINDOW_SIZE => { validator => sub { $_[0] <= MAX_FCW_SIZE; }, error => FLOW_CONTROL_ERROR }, ); my %s_action = ( &SETTINGS_INITIAL_WINDOW_SIZE => sub { my ( $con, $size ) = @_; $con->fcw_initial_change($size); } ); sub decode { my ( $con, $buf_ref, $buf_offset, $length ) = @_; my $frame_ref = $con->decode_context->{frame}; if ( $frame_ref->{stream} != 0 ) { $con->error(PROTOCOL_ERROR); return undef; } # just ack for our previous settings if ( $frame_ref->{flags} & ACK ) { if ( $length != 0 ) { tracer->error( "ACK settings frame have non-zero ($length) payload\n"); $con->error(FRAME_SIZE_ERROR); return undef; } return 0 # received empty settings (default), accept it } elsif ( $length == 0 ) { $con->accept_settings(); return 0; } if ( $length % 6 != 0 ) { tracer->error("Settings frame payload is broken (lenght $length)\n"); $con->error(FRAME_SIZE_ERROR); return undef; } my @settings = unpack( '(nN)*', substr( $$buf_ref, $buf_offset, $length ) ); while ( my ( $key, $value ) = splice @settings, 0, 2 ) { if ( !defined $con->enc_setting($key) ) { tracer->debug("\tUnknown setting $key\n"); # ignore unknown setting next; } elsif ( exists $s_check{$key} && !$s_check{$key}{validator}->($value) ) { tracer->debug( "\tInvalid value of setting " . const_name( "settings", $key ) . ": " . $value ); $con->error( $s_check{$key}{error} ); return undef; } # Settings change may run some action $s_action{$key}->( $con, $value ) if exists $s_action{$key}; tracer->debug( "\tSettings " . const_name( "settings", $key ) . " = $value\n" ); $con->enc_setting( $key, $value ); } $con->accept_settings(); return $length; } sub encode { my ( $con, $flags_ref, $stream, $data ) = @_; my $payload = ''; for my $key ( sort keys %$data ) { tracer->debug( "\tSettings " . const_name( "settings", $key ) . " = $data->{$key}\n" ); $payload .= pack( 'nN', $key, $data->{$key} ); } return $payload; } 1; Window_update.pm100644000764000764 272313562447335 22742 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/lib/Protocol/HTTP2/Framepackage Protocol::HTTP2::Frame::Window_update; use strict; use warnings; use Protocol::HTTP2::Constants qw(:flags :errors :limits); use Protocol::HTTP2::Trace qw(tracer); sub decode { my ( $con, $buf_ref, $buf_offset, $length ) = @_; my $frame_ref = $con->decode_context->{frame}; if ( $length != 4 ) { tracer->error( "Received windows_update frame with invalid length $length"); $con->error(FRAME_SIZE_ERROR); return undef; } my $fcw_add = unpack 'N', substr $$buf_ref, $buf_offset, 4; $fcw_add &= 0x7FFF_FFFF; if ( $fcw_add == 0 ) { tracer->error("Received flow-control window increment of 0"); $con->error(PROTOCOL_ERROR); return undef; } if ( $frame_ref->{stream} == 0 ) { if ( $con->fcw_send($fcw_add) > MAX_FCW_SIZE ) { $con->error(FLOW_CONTROL_ERROR); } else { $con->send_blocked(); } } else { my $fcw = $con->stream_fcw_send( $frame_ref->{stream}, $fcw_add ); if ( defined $fcw && $fcw > MAX_FCW_SIZE ) { tracer->warning("flow-control window size exceeded MAX_FCW_SIZE"); $con->stream_error( $frame_ref->{stream}, FLOW_CONTROL_ERROR ); } elsif ( defined $fcw ) { $con->stream_send_blocked( $frame_ref->{stream} ); } } return $length; } sub encode { my ( $con, $flags_ref, $stream, $data ) = @_; return pack 'N', $data; } 1; HeaderCompression.pm100644000764000764 2657513562447335 22544 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/lib/Protocol/HTTP2package Protocol::HTTP2::HeaderCompression; use strict; use warnings; use Protocol::HTTP2::Huffman; use Protocol::HTTP2::StaticTable; use Protocol::HTTP2::Constants qw(:errors :settings :limits); use Protocol::HTTP2::Trace qw(tracer bin2hex); use Exporter qw(import); our @EXPORT_OK = qw(int_encode int_decode str_encode str_decode headers_decode headers_encode); sub int_encode { my ( $int, $N ) = @_; $N ||= 7; my $ff = ( 1 << $N ) - 1; if ( $int < $ff ) { return pack 'C', $int; } my $res = pack 'C', $ff; $int -= $ff; while ( $int >= 0x80 ) { $res .= pack( 'C', ( $int & 0x7f ) | 0x80 ); $int >>= 7; } return $res . pack( 'C', $int ); } # int_decode() # # arguments: # buf_ref - ref to buffer with encoded data # buf_offset - offset in buffer # int_ref - ref to scalar where result will be stored # N - bits in first byte # # returns: count of readed bytes of encoded integer # or undef on error (malformed data) sub int_decode { my ( $buf_ref, $buf_offset, $int_ref, $N ) = @_; return undef if length($$buf_ref) - $buf_offset <= 0; $N ||= 7; my $ff = ( 1 << $N ) - 1; $$int_ref = $ff & vec( $$buf_ref, $buf_offset, 8 ); return 1 if $$int_ref < $ff; my $l = length($$buf_ref) - $buf_offset - 1; for my $i ( 1 .. $l ) { return undef if $i > MAX_INT_SIZE; my $s = vec( $$buf_ref, $i + $buf_offset, 8 ); $$int_ref += ( $s & 0x7f ) << ( $i - 1 ) * 7; return $i + 1 if $s < 0x80; } return undef; } sub str_encode { my $str = shift; my $huff_str = huffman_encode($str); my $pack; if ( length($huff_str) < length($str) ) { $pack = int_encode( length($huff_str), 7 ); vec( $pack, 7, 1 ) = 1; $pack .= $huff_str; } else { $pack = int_encode( length($str), 7 ); $pack .= $str; } return $pack; } # str_decode() # arguments: # buf_ref - ref to buffer with encoded data # buf_offset - offset in buffer # str_ref - ref to scalar where result will be stored # returns: count of readed bytes of encoded data sub str_decode { my ( $buf_ref, $buf_offset, $str_ref ) = @_; my $offset = int_decode( $buf_ref, $buf_offset, \my $l, 7 ); return undef unless defined $offset && length($$buf_ref) - $buf_offset - $offset >= $l; $$str_ref = substr $$buf_ref, $offset + $buf_offset, $l; $$str_ref = huffman_decode($$str_ref) if vec( $$buf_ref, $buf_offset * 8 + 7, 1 ) == 1; return $offset + $l; } sub evict_ht { my ( $context, $size ) = @_; my @evicted; my $ht = $context->{header_table}; while ( $context->{ht_size} + $size > $context->{max_ht_size} ) { my $n = $#$ht; my $kv_ref = pop @$ht; $context->{ht_size} -= 32 + length( $kv_ref->[0] ) + length( $kv_ref->[1] ); tracer->debug( sprintf "Evicted header [%i] %s = %s\n", $n + 1, @$kv_ref ); push @evicted, [ $n, @$kv_ref ]; } return @evicted; } sub add_to_ht { my ( $context, $key, $value ) = @_; my $size = length($key) + length($value) + 32; return () if $size > $context->{max_ht_size}; my @evicted = evict_ht( $context, $size ); my $ht = $context->{header_table}; my $kv_ref = [ $key, $value ]; unshift @$ht, $kv_ref; $context->{ht_size} += $size; return @evicted; } sub headers_decode { my ( $con, $buf_ref, $buf_offset, $length, $stream_id ) = @_; my $context = $con->decode_context; my $ht = $context->{header_table}; my $eh = $context->{emitted_headers}; my $offset = 0; while ( $offset < $length ) { my $f = vec( $$buf_ref, $buf_offset + $offset, 8 ); tracer->debug( sprintf "\toffset: %d, byte: %02x\n", $offset, $f ); # Indexed Header if ( $f & 0x80 ) { my $size = int_decode( $buf_ref, $buf_offset + $offset, \my $index, 7 ); last unless $size; # DECODING ERROR if ( $index == 0 ) { tracer->error("Indexed header with zero index\n"); $con->error(COMPRESSION_ERROR); return undef; } tracer->debug("\tINDEXED($index) HEADER\t"); # Static table or Header Table entry if ( $index <= @stable ) { my ( $key, $value ) = @{ $stable[ $index - 1 ] }; push @$eh, $key, $value; tracer->debug("$key = $value\n"); } elsif ( $index > @stable + @$ht ) { tracer->error( "Indexed header with index out of header table: " . $index . "\n" ); $con->error(COMPRESSION_ERROR); return undef; } else { my $kv_ref = $ht->[ $index - @stable - 1 ]; push @$eh, @$kv_ref; tracer->debug("$kv_ref->[0] = $kv_ref->[1]\n"); } $offset += $size; } # Literal Header Field - New Name elsif ( $f == 0x40 || $f == 0x00 || $f == 0x10 ) { my $key_size = str_decode( $buf_ref, $buf_offset + $offset + 1, \my $key ); last unless $key_size; if ( $key_size == 1 ) { tracer->error("Empty literal header name"); $con->error(COMPRESSION_ERROR); return undef; } if ( $key =~ /[^a-z0-9\!\#\$\%\&\'\*\+\-\^\_\`]/ && $key !~ /^\:/ ) { tracer->warning("Illegal characters in header name"); $con->stream_error( $stream_id, PROTOCOL_ERROR ); return undef; } my $value_size = str_decode( $buf_ref, $buf_offset + $offset + 1 + $key_size, \my $value ); last unless $value_size; # Emitting header push @$eh, $key, $value; # Add to index if ( $f == 0x40 ) { add_to_ht( $context, $key, $value ); } tracer->debug( sprintf "\tLITERAL(new) HEADER\t%s: %s\n", $key, substr( $value, 0, 30 ) ); $offset += 1 + $key_size + $value_size; } # Literal Header Field - Indexed Name elsif (( $f & 0xC0 ) == 0x40 || ( $f & 0xF0 ) == 0x00 || ( $f & 0xF0 ) == 0x10 ) { my $size = int_decode( $buf_ref, $buf_offset + $offset, \my $index, ( $f & 0xC0 ) == 0x40 ? 6 : 4 ); last unless $size; my $value_size = str_decode( $buf_ref, $buf_offset + $offset + $size, \my $value ); last unless $value_size; my $key; if ( $index <= @stable ) { $key = $stable[ $index - 1 ]->[0]; } elsif ( $index > @stable + @$ht ) { tracer->error( "Literal header with index out of header table: " . $index . "\n" ); $con->error(COMPRESSION_ERROR); return undef; } else { $key = $ht->[ $index - @stable - 1 ]->[0]; } # Emitting header push @$eh, $key, $value; # Add to index if ( ( $f & 0xC0 ) == 0x40 ) { add_to_ht( $context, $key, $value ); } tracer->debug("\tLITERAL($index) HEADER\t$key: $value\n"); $offset += $size + $value_size; } # Encoding Context Update - Maximum Header Table Size change elsif ( ( $f & 0xE0 ) == 0x20 ) { my $size = int_decode( $buf_ref, $buf_offset + $offset, \my $ht_size, 5 ); last unless $size; # It's not possible to increase size of HEADER_TABLE if ( $ht_size > $context->{settings}->{&SETTINGS_HEADER_TABLE_SIZE} ) { tracer->error( "Peer attempt to increase " . "maximum header table size higher than current size: " . "$ht_size > " . $context->{settings}->{&SETTINGS_HEADER_TABLE_SIZE} ); $con->error(COMPRESSION_ERROR); return undef; } if (@$eh) { tracer->error( "Attempt to change header table size after headers"); $con->error(COMPRESSION_ERROR); return undef; } tracer->debug( "Update header table size from " . $context->{max_ht_size} . " to " . $ht_size ); $context->{max_ht_size} = $ht_size; evict_ht( $context, 0 ); $offset += $size; } # Encoding Error else { tracer->error( sprintf( "Unknown header type: %08b", $f ) ); $con->error(COMPRESSION_ERROR); return undef; } } if ( $offset != $length ) { tracer->error( "Headers decoding stopped at offset $offset of $length\n"); $con->error(COMPRESSION_ERROR); return undef; } return $offset; } sub headers_encode { my ( $context, $headers ) = @_; my $res = ''; my $ht = $context->{header_table}; my $sht = $context->{settings}->{&SETTINGS_HEADER_TABLE_SIZE}; # Encode dynamic table size update if ( $context->{max_ht_size} != $sht ) { $res .= int_encode( $sht, 5 ); vec( $res, 3, 2 ) = 0; vec( $res, 5, 1 ) = 1; $context->{max_ht_size} = $sht; } HLOOP: for my $n ( 0 .. $#$headers / 2 ) { my $header = lc( $headers->[ 2 * $n ] ); my $value = $headers->[ 2 * $n + 1 ]; my $hdr; tracer->debug("Encoding header: $header = $value\n"); for my $i ( 0 .. $#$ht ) { next unless $ht->[$i]->[0] eq $header && $ht->[$i]->[1] eq $value; $hdr = int_encode( $i + @stable + 1, 7 ); vec( $hdr, 7, 1 ) = 1; $res .= $hdr; tracer->debug( "\talready in header table, index " . ( $i + 1 ) . "\n" ); next HLOOP; } # 7.1 Indexed header field representation if ( exists $rstable{ $header . ' ' . $value } ) { $hdr = int_encode( $rstable{ $header . ' ' . $value }, 7 ); vec( $hdr, 7, 1 ) = 1; tracer->debug( "\tIndexed header " . $rstable{ $header . ' ' . $value } . " from table\n" ); } # 7.2.1 Literal Header Field with Incremental Indexing # (Indexed Name) elsif ( exists $rstable{ $header . ' ' } ) { $hdr = int_encode( $rstable{ $header . ' ' }, 6 ); vec( $hdr, 3, 2 ) = 1; $hdr .= str_encode($value); add_to_ht( $context, $header, $value ); tracer->debug( "\tLiteral header " . $rstable{ $header . ' ' } . " indexed name\n" ); } # 7.2.1 Literal Header Field with Incremental Indexing # (New Name) else { $hdr = pack( 'C', 0x40 ); $hdr .= str_encode($header) . str_encode($value); add_to_ht( $context, $header, $value ); tracer->debug("\tLiteral header new name\n"); } $res .= $hdr; } return $res; } 1; Huffman.pm100644000764000764 225313562447335 20461 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/lib/Protocol/HTTP2package Protocol::HTTP2::Huffman; use strict; use warnings; use Protocol::HTTP2::HuffmanCodes; use Protocol::HTTP2::Trace qw(tracer); our ( %hcodes, %rhcodes, $hre ); require Exporter; our @ISA = qw(Exporter); our @EXPORT = qw(huffman_encode huffman_decode); # Memory unefficient algorithm (well suited for short strings) sub huffman_encode { my $s = shift; my $ret = my $bin = ''; for my $i ( 0 .. length($s) - 1 ) { $bin .= $hcodes{ ord( substr $s, $i, 1 ) }; } $bin .= substr( $hcodes{256}, 0, 8 - length($bin) % 8 ) if length($bin) % 8; return $ret . pack( 'B*', $bin ); } sub huffman_decode { my $s = shift; my $bin = unpack( 'B*', $s ); my $c = 0; $s = pack 'C*', map { $c += length; $rhcodes{$_} } ( $bin =~ /$hre/g ); tracer->warning( sprintf( "malformed data in string at position %i, " . " length: %i", $c, length($bin) ) ) if length($bin) - $c > 8; tracer->warning( sprintf "no huffman code 256 at the end of encoded string '%s': %s\n", substr( $s, 0, 30 ), substr( $bin, $c ) ) if $hcodes{256} !~ /^@{[ substr($bin, $c) ]}/; return $s; } 1; HuffmanCodes.pm100644000764000764 2121613562447335 21457 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/lib/Protocol/HTTP2package Protocol::HTTP2::HuffmanCodes; use strict; use warnings; require Exporter; our @ISA = qw(Exporter); our ( %hcodes, %rhcodes, $hre ); our @EXPORT = qw(%hcodes %rhcodes $hre); %hcodes = ( 0 => '1111111111000', 1 => '11111111111111111011000', 2 => '1111111111111111111111100010', 3 => '1111111111111111111111100011', 4 => '1111111111111111111111100100', 5 => '1111111111111111111111100101', 6 => '1111111111111111111111100110', 7 => '1111111111111111111111100111', 8 => '1111111111111111111111101000', 9 => '111111111111111111101010', 10 => '111111111111111111111111111100', 11 => '1111111111111111111111101001', 12 => '1111111111111111111111101010', 13 => '111111111111111111111111111101', 14 => '1111111111111111111111101011', 15 => '1111111111111111111111101100', 16 => '1111111111111111111111101101', 17 => '1111111111111111111111101110', 18 => '1111111111111111111111101111', 19 => '1111111111111111111111110000', 20 => '1111111111111111111111110001', 21 => '1111111111111111111111110010', 22 => '111111111111111111111111111110', 23 => '1111111111111111111111110011', 24 => '1111111111111111111111110100', 25 => '1111111111111111111111110101', 26 => '1111111111111111111111110110', 27 => '1111111111111111111111110111', 28 => '1111111111111111111111111000', 29 => '1111111111111111111111111001', 30 => '1111111111111111111111111010', 31 => '1111111111111111111111111011', 32 => '010100', 33 => '1111111000', 34 => '1111111001', 35 => '111111111010', 36 => '1111111111001', 37 => '010101', 38 => '11111000', 39 => '11111111010', 40 => '1111111010', 41 => '1111111011', 42 => '11111001', 43 => '11111111011', 44 => '11111010', 45 => '010110', 46 => '010111', 47 => '011000', 48 => '00000', 49 => '00001', 50 => '00010', 51 => '011001', 52 => '011010', 53 => '011011', 54 => '011100', 55 => '011101', 56 => '011110', 57 => '011111', 58 => '1011100', 59 => '11111011', 60 => '111111111111100', 61 => '100000', 62 => '111111111011', 63 => '1111111100', 64 => '1111111111010', 65 => '100001', 66 => '1011101', 67 => '1011110', 68 => '1011111', 69 => '1100000', 70 => '1100001', 71 => '1100010', 72 => '1100011', 73 => '1100100', 74 => '1100101', 75 => '1100110', 76 => '1100111', 77 => '1101000', 78 => '1101001', 79 => '1101010', 80 => '1101011', 81 => '1101100', 82 => '1101101', 83 => '1101110', 84 => '1101111', 85 => '1110000', 86 => '1110001', 87 => '1110010', 88 => '11111100', 89 => '1110011', 90 => '11111101', 91 => '1111111111011', 92 => '1111111111111110000', 93 => '1111111111100', 94 => '11111111111100', 95 => '100010', 96 => '111111111111101', 97 => '00011', 98 => '100011', 99 => '00100', 100 => '100100', 101 => '00101', 102 => '100101', 103 => '100110', 104 => '100111', 105 => '00110', 106 => '1110100', 107 => '1110101', 108 => '101000', 109 => '101001', 110 => '101010', 111 => '00111', 112 => '101011', 113 => '1110110', 114 => '101100', 115 => '01000', 116 => '01001', 117 => '101101', 118 => '1110111', 119 => '1111000', 120 => '1111001', 121 => '1111010', 122 => '1111011', 123 => '111111111111110', 124 => '11111111100', 125 => '11111111111101', 126 => '1111111111101', 127 => '1111111111111111111111111100', 128 => '11111111111111100110', 129 => '1111111111111111010010', 130 => '11111111111111100111', 131 => '11111111111111101000', 132 => '1111111111111111010011', 133 => '1111111111111111010100', 134 => '1111111111111111010101', 135 => '11111111111111111011001', 136 => '1111111111111111010110', 137 => '11111111111111111011010', 138 => '11111111111111111011011', 139 => '11111111111111111011100', 140 => '11111111111111111011101', 141 => '11111111111111111011110', 142 => '111111111111111111101011', 143 => '11111111111111111011111', 144 => '111111111111111111101100', 145 => '111111111111111111101101', 146 => '1111111111111111010111', 147 => '11111111111111111100000', 148 => '111111111111111111101110', 149 => '11111111111111111100001', 150 => '11111111111111111100010', 151 => '11111111111111111100011', 152 => '11111111111111111100100', 153 => '111111111111111011100', 154 => '1111111111111111011000', 155 => '11111111111111111100101', 156 => '1111111111111111011001', 157 => '11111111111111111100110', 158 => '11111111111111111100111', 159 => '111111111111111111101111', 160 => '1111111111111111011010', 161 => '111111111111111011101', 162 => '11111111111111101001', 163 => '1111111111111111011011', 164 => '1111111111111111011100', 165 => '11111111111111111101000', 166 => '11111111111111111101001', 167 => '111111111111111011110', 168 => '11111111111111111101010', 169 => '1111111111111111011101', 170 => '1111111111111111011110', 171 => '111111111111111111110000', 172 => '111111111111111011111', 173 => '1111111111111111011111', 174 => '11111111111111111101011', 175 => '11111111111111111101100', 176 => '111111111111111100000', 177 => '111111111111111100001', 178 => '1111111111111111100000', 179 => '111111111111111100010', 180 => '11111111111111111101101', 181 => '1111111111111111100001', 182 => '11111111111111111101110', 183 => '11111111111111111101111', 184 => '11111111111111101010', 185 => '1111111111111111100010', 186 => '1111111111111111100011', 187 => '1111111111111111100100', 188 => '11111111111111111110000', 189 => '1111111111111111100101', 190 => '1111111111111111100110', 191 => '11111111111111111110001', 192 => '11111111111111111111100000', 193 => '11111111111111111111100001', 194 => '11111111111111101011', 195 => '1111111111111110001', 196 => '1111111111111111100111', 197 => '11111111111111111110010', 198 => '1111111111111111101000', 199 => '1111111111111111111101100', 200 => '11111111111111111111100010', 201 => '11111111111111111111100011', 202 => '11111111111111111111100100', 203 => '111111111111111111111011110', 204 => '111111111111111111111011111', 205 => '11111111111111111111100101', 206 => '111111111111111111110001', 207 => '1111111111111111111101101', 208 => '1111111111111110010', 209 => '111111111111111100011', 210 => '11111111111111111111100110', 211 => '111111111111111111111100000', 212 => '111111111111111111111100001', 213 => '11111111111111111111100111', 214 => '111111111111111111111100010', 215 => '111111111111111111110010', 216 => '111111111111111100100', 217 => '111111111111111100101', 218 => '11111111111111111111101000', 219 => '11111111111111111111101001', 220 => '1111111111111111111111111101', 221 => '111111111111111111111100011', 222 => '111111111111111111111100100', 223 => '111111111111111111111100101', 224 => '11111111111111101100', 225 => '111111111111111111110011', 226 => '11111111111111101101', 227 => '111111111111111100110', 228 => '1111111111111111101001', 229 => '111111111111111100111', 230 => '111111111111111101000', 231 => '11111111111111111110011', 232 => '1111111111111111101010', 233 => '1111111111111111101011', 234 => '1111111111111111111101110', 235 => '1111111111111111111101111', 236 => '111111111111111111110100', 237 => '111111111111111111110101', 238 => '11111111111111111111101010', 239 => '11111111111111111110100', 240 => '11111111111111111111101011', 241 => '111111111111111111111100110', 242 => '11111111111111111111101100', 243 => '11111111111111111111101101', 244 => '111111111111111111111100111', 245 => '111111111111111111111101000', 246 => '111111111111111111111101001', 247 => '111111111111111111111101010', 248 => '111111111111111111111101011', 249 => '1111111111111111111111111110', 250 => '111111111111111111111101100', 251 => '111111111111111111111101101', 252 => '111111111111111111111101110', 253 => '111111111111111111111101111', 254 => '111111111111111111111110000', 255 => '11111111111111111111101110', 256 => '111111111111111111111111111111', ); %rhcodes = reverse %hcodes; { local $" = '|'; $hre = qr/(?:^|\G)(@{[ keys %rhcodes ]})/; } 1; Server.pm100644000764000764 3305113562447335 20363 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/lib/Protocol/HTTP2package Protocol::HTTP2::Server; use strict; use warnings; use Protocol::HTTP2::Connection; use Protocol::HTTP2::Constants qw(:frame_types :flags :states :endpoints :settings :limits const_name); use Protocol::HTTP2::Trace qw(tracer); use Carp; use Scalar::Util (); =encoding utf-8 =head1 NAME Protocol::HTTP2::Server - HTTP/2 server =head1 SYNOPSIS use Protocol::HTTP2::Server; # You must create tcp server yourself use AnyEvent; use AnyEvent::Socket; use AnyEvent::Handle; my $w = AnyEvent->condvar; # Plain-text HTTP/2 connection tcp_server 'localhost', 8000, sub { my ( $fh, $peer_host, $peer_port ) = @_; my $handle; $handle = AnyEvent::Handle->new( fh => $fh, autocork => 1, on_error => sub { $_[0]->destroy; print "connection error\n"; }, on_eof => sub { $handle->destroy; } ); # Create Protocol::HTTP2::Server object my $server; $server = Protocol::HTTP2::Server->new( on_request => sub { my ( $stream_id, $headers, $data ) = @_; my $message = "hello, world!"; # Response to client $server->response( ':status' => 200, stream_id => $stream_id, # HTTP/1.1 Headers headers => [ 'server' => 'perl-Protocol-HTTP2/0.13', 'content-length' => length($message), 'cache-control' => 'max-age=3600', 'date' => 'Fri, 18 Apr 2014 07:27:11 GMT', 'last-modified' => 'Thu, 27 Feb 2014 10:30:37 GMT', ], # Content data => $message, ); }, ); # First send settings to peer while ( my $frame = $server->next_frame ) { $handle->push_write($frame); } # Receive clients frames # Reply to client $handle->on_read( sub { my $handle = shift; $server->feed( $handle->{rbuf} ); $handle->{rbuf} = undef; while ( my $frame = $server->next_frame ) { $handle->push_write($frame); } $handle->push_shutdown if $server->shutdown; } ); }; $w->recv; =head1 DESCRIPTION Protocol::HTTP2::Server is HTTP/2 server library. It's intended to make http2-server implementations on top of your favorite event loop. See also L - AnyEvent HTTP/2 Server for PSGI based on L. =head2 METHODS =head3 new Initialize new server object my $server = Procotol::HTTP2::Client->new( %options ); Available options: =over =item on_request => sub {...} Callback invoked when receiving client's requests on_request => sub { # Stream ID, headers array reference and body of request my ( $stream_id, $headers, $data ) = @_; my $message = "hello, world!"; $server->response( ':status' => 200, stream_id => $stream_id, headers => [ 'server' => 'perl-Protocol-HTTP2/0.13', 'content-length' => length($message), ], data => $message, ); ... }, =item upgrade => 0|1 Use HTTP/1.1 Upgrade to upgrade protocol from HTTP/1.1 to HTTP/2. Upgrade possible only on plain (non-tls) connection. See L =item on_error => sub {...} Callback invoked on protocol errors on_error => sub { my $error = shift; ... }, =item on_change_state => sub {...} Callback invoked every time when http/2 streams change their state. See L on_change_state => sub { my ( $stream_id, $previous_state, $current_state ) = @_; ... }, =back =cut sub new { my ( $class, %opts ) = @_; my $self = { con => undef, input => '', settings => { &SETTINGS_MAX_CONCURRENT_STREAMS => DEFAULT_MAX_CONCURRENT_STREAMS, exists $opts{settings} ? %{ delete $opts{settings} } : () }, }; if ( exists $opts{on_request} ) { Scalar::Util::weaken( my $self = $self ); $self->{cb} = delete $opts{on_request}; $opts{on_new_peer_stream} = sub { my $stream_id = shift; $self->{con}->stream_cb( $stream_id, HALF_CLOSED, sub { $self->{cb}->( $stream_id, $self->{con}->stream_headers($stream_id), $self->{con}->stream_data($stream_id), ); } ); } } $self->{con} = Protocol::HTTP2::Connection->new( SERVER, %opts, settings => $self->{settings} ); $self->{con}->enqueue( SETTINGS, 0, 0, $self->{settings} ) unless $self->{con}->upgrade; bless $self, $class; } =head3 response Prepare response my $message = "hello, world!"; $server->response( # HTTP/2 status ':status' => 200, # Stream ID stream_id => $stream_id, # HTTP/1.1 headers headers => [ 'server' => 'perl-Protocol-HTTP2/0.01', 'content-length' => length($message), ], # Body of response data => $message, ); =cut my @must = (qw(:status)); sub response { my ( $self, %h ) = @_; my @miss = grep { !exists $h{$_} } @must; croak "Missing headers in response: @miss" if @miss; my $con = $self->{con}; $con->send_headers( $h{stream_id}, [ ( map { $_ => $h{$_} } @must ), exists $h{headers} ? @{ $h{headers} } : () ], exists $h{data} ? 0 : 1 ); $con->send_data( $h{stream_id}, $h{data}, 1 ) if exists $h{data}; return $self; } =head3 response_stream If body of response is not yet ready or server will stream data # P::H::Server::Stream object my $server_stream; $server_stream = $server->response_stream( # HTTP/2 status ':status' => 200, # Stream ID stream_id => $stream_id, # HTTP/1.1 headers headers => [ 'server' => 'perl-Protocol-HTTP2/0.01', ], # Callback if client abort this stream on_cancel => sub { ... } ); # Send partial data $server_stream->send($chunk_of_data); $server_stream->send($chunk_of_data); ## 3 ways to finish stream: # # The best: send last chunk and close stream in one action $server_stream->last($chunk_of_data); # Close the stream (will send empty frame) $server_stream->close(); # Destroy object (will send empty frame) undef $server_stream =cut { package Protocol::HTTP2::Server::Stream; use Protocol::HTTP2::Constants qw(:states); use Scalar::Util (); sub new { my ( $class, %opts ) = @_; my $self = bless {%opts}, $class; if ( my $on_cancel = $self->{on_cancel} ) { Scalar::Util::weaken( my $self = $self ); $self->{con}->stream_cb( $self->{stream_id}, CLOSED, sub { return if $self->{done}; $self->{done} = 1; $on_cancel->(); } ); } $self; } sub send { my $self = shift; $self->{con}->send_data( $self->{stream_id}, shift ); } sub last { my $self = shift; $self->{done} = 1; $self->{con}->send_data( $self->{stream_id}, shift, 1 ); } sub close { my $self = shift; $self->{done} = 1; $self->{con}->send_data( $self->{stream_id}, undef, 1 ); } sub DESTROY { my $self = shift; $self->{con}->send_data( $self->{stream_id}, undef, 1 ) unless $self->{done} || !$self->{con}; } } sub response_stream { my ( $self, %h ) = @_; my @miss = grep { !exists $h{$_} } @must; croak "Missing headers in response_stream: @miss" if @miss; my $con = $self->{con}; $con->send_headers( $h{stream_id}, [ ( map { $_ => $h{$_} } @must ), exists $h{headers} ? @{ $h{headers} } : () ], 0 ); return Protocol::HTTP2::Server::Stream->new( con => $con, stream_id => $h{stream_id}, on_cancel => $h{on_cancel}, ); } =head3 push Prepare Push Promise. See L # Example of push inside of on_request callback on_request => sub { my ( $stream_id, $headers, $data ) = @_; my %h = (@$headers); # Push promise (must be before response) if ( $h{':path'} eq '/index.html' ) { # index.html contain styles.css resource, so server can push # "/style.css" to client before it request it to increase speed # of loading of whole page $server->push( ':authority' => 'locahost:8000', ':method' => 'GET', ':path' => '/style.css', ':scheme' => 'http', stream_id => $stream_id, ); } $server->response(...); ... } =cut my @must_pp = (qw(:authority :method :path :scheme)); sub push { my ( $self, %h ) = @_; my $con = $self->{con}; my @miss = grep { !exists $h{$_} } @must_pp; croak "Missing headers in push promise: @miss" if @miss; croak "Can't push on my own stream. " . "Seems like a recursion in request callback." if $h{stream_id} % 2 == 0; my $promised_sid = $con->new_stream; $con->stream_promised_sid( $h{stream_id}, $promised_sid ); my @headers = map { $_ => $h{$_} } @must_pp; $con->send_pp_headers( $h{stream_id}, $promised_sid, \@headers, ); # send promised response after current stream is closed $con->stream_cb( $h{stream_id}, CLOSED, sub { $self->{cb}->( $promised_sid, \@headers ); } ); return $self; } =head3 shutdown Get connection status: =over =item 0 - active =item 1 - closed (you can terminate connection) =back =cut sub shutdown { shift->{con}->shutdown; } =head3 next_frame get next frame to send over connection to client. Returns: =over =item undef - on error =item 0 - nothing to send =item binary string - encoded frame =back # Example while ( my $frame = $server->next_frame ) { syswrite $fh, $frame; } =cut sub next_frame { my $self = shift; my $frame = $self->{con}->dequeue; if ($frame) { my ( $length, $type, $flags, $stream_id ) = $self->{con}->frame_header_decode( \$frame, 0 ); tracer->debug( sprintf "Send one frame to a wire:" . " type(%s), length(%i), flags(%08b), sid(%i)\n", const_name( 'frame_types', $type ), $length, $flags, $stream_id ); } return $frame; } =head3 feed Feed decoder with chunks of client's request sysread $fh, $binary_data, 4096; $server->feed($binary_data); =cut sub feed { my ( $self, $chunk ) = @_; $self->{input} .= $chunk; my $offset = 0; my $con = $self->{con}; tracer->debug( "got " . length($chunk) . " bytes on a wire\n" ); if ( $con->upgrade ) { my @headers; my $len = $con->decode_upgrade_request( \$self->{input}, $offset, \@headers ); $con->shutdown(1) unless defined $len; return unless $len; substr( $self->{input}, $offset, $len ) = ''; $con->enqueue_raw( $con->upgrade_response ); $con->enqueue( SETTINGS, 0, 0, { &SETTINGS_MAX_CONCURRENT_STREAMS => DEFAULT_MAX_CONCURRENT_STREAMS } ); $con->upgrade(0); # The HTTP/1.1 request that is sent prior to upgrade is assigned stream # identifier 1 and is assigned default priority values (Section 5.3.5). # Stream 1 is implicitly half closed from the client toward the server, # since the request is completed as an HTTP/1.1 request. After # commencing the HTTP/2 connection, stream 1 is used for the response. $con->new_peer_stream(1); $con->stream_headers( 1, \@headers ); $con->stream_state( 1, HALF_CLOSED ); } if ( !$con->preface ) { my $len = $con->preface_decode( \$self->{input}, $offset ); unless ( defined $len ) { tracer->error("invalid preface. shutdown connection\n"); $con->shutdown(1); } return unless $len; tracer->debug("got preface\n"); $offset += $len; $con->preface(1); } while ( my $len = $con->frame_decode( \$self->{input}, $offset ) ) { tracer->debug("decoded frame at $offset, length $len\n"); $offset += $len; } substr( $self->{input}, 0, $offset ) = '' if $offset; } =head3 ping Send ping frame to client (to keep connection alive) $server->ping or $server->ping($payload); Payload can be arbitrary binary string and must contain 8 octets. If payload argument is omitted server will send random data. =cut sub ping { shift->{con}->send_ping(@_); } 1; StaticTable.pm100644000764000764 605613562447335 21301 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/lib/Protocol/HTTP2package Protocol::HTTP2::StaticTable; use strict; use warnings; require Exporter; our @ISA = qw(Exporter); our ( @stable, %rstable ); our @EXPORT = qw(@stable %rstable); @stable = ( [ ":authority", "" ], [ ":method", "GET" ], [ ":method", "POST" ], [ ":path", "/" ], [ ":path", "/index.html" ], [ ":scheme", "http" ], [ ":scheme", "https" ], [ ":status", "200" ], [ ":status", "204" ], [ ":status", "206" ], [ ":status", "304" ], [ ":status", "400" ], [ ":status", "404" ], [ ":status", "500" ], [ "accept-charset", "" ], [ "accept-encoding", "gzip, deflate" ], [ "accept-language", "" ], [ "accept-ranges", "" ], [ "accept", "" ], [ "access-control-allow-origin", "" ], [ "age", "" ], [ "allow", "" ], [ "authorization", "" ], [ "cache-control", "" ], [ "content-disposition", "" ], [ "content-encoding", "" ], [ "content-language", "" ], [ "content-length", "" ], [ "content-location", "" ], [ "content-range", "" ], [ "content-type", "" ], [ "cookie", "" ], [ "date", "" ], [ "etag", "" ], [ "expect", "" ], [ "expires", "" ], [ "from", "" ], [ "host", "" ], [ "if-match", "" ], [ "if-modified-since", "" ], [ "if-none-match", "" ], [ "if-range", "" ], [ "if-unmodified-since", "" ], [ "last-modified", "" ], [ "link", "" ], [ "location", "" ], [ "max-forwards", "" ], [ "proxy-authenticate", "" ], [ "proxy-authorization", "" ], [ "range", "" ], [ "referer", "" ], [ "refresh", "" ], [ "retry-after", "" ], [ "server", "" ], [ "set-cookie", "" ], [ "strict-transport-security", "" ], [ "transfer-encoding", "" ], [ "user-agent", "" ], [ "vary", "" ], [ "via", "" ], [ "www-authenticate", "" ], ); for my $k ( 0 .. $#stable ) { my $key = join ' ', @{ $stable[$k] }; $rstable{$key} = $k + 1; $rstable{ $stable[$k]->[0] . ' ' } = $k + 1 if ( $stable[$k]->[1] ne '' && !exists $rstable{ $stable[$k]->[0] . ' ' } ); } 1; Stream.pm100644000764000764 3004313562447335 20346 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/lib/Protocol/HTTP2package Protocol::HTTP2::Stream; use strict; use warnings; use Protocol::HTTP2::Constants qw(:states :endpoints :settings :frame_types :limits :errors); use Protocol::HTTP2::HeaderCompression qw( headers_decode ); use Protocol::HTTP2::Trace qw(tracer); # Streams related part of Protocol::HTTP2::Conntection # Autogen properties { no strict 'refs'; for my $prop ( qw(promised_sid headers pp_headers header_block trailer trailer_headers length blocked_data weight end reset) ) { *{ __PACKAGE__ . '::stream_' . $prop } = sub { return !exists $_[0]->{streams}->{ $_[1] } ? undef : @_ == 2 ? $_[0]->{streams}->{ $_[1] }->{$prop} : ( $_[0]->{streams}->{ $_[1] }->{$prop} = $_[2] ); } } } sub new_stream { my $self = shift; return undef if $self->goaway; $self->{last_stream} += 2 if exists $self->{streams}->{ $self->{type} == CLIENT ? 1 : 2 }; $self->{streams}->{ $self->{last_stream} } = { 'state' => IDLE, 'weight' => DEFAULT_WEIGHT, 'stream_dep' => 0, 'fcw_recv' => $self->dec_setting(SETTINGS_INITIAL_WINDOW_SIZE), 'fcw_send' => $self->enc_setting(SETTINGS_INITIAL_WINDOW_SIZE), }; return $self->{last_stream}; } sub new_peer_stream { my $self = shift; my $stream_id = shift; if ( $stream_id < $self->{last_peer_stream} || ( $stream_id % 2 ) == ( $self->{type} == CLIENT ) ? 1 : 0 || $self->goaway ) { tracer->error("Peer send invalid stream id: $stream_id\n"); $self->error(PROTOCOL_ERROR); return undef; } $self->{last_peer_stream} = $stream_id; if ( $self->dec_setting(SETTINGS_MAX_CONCURRENT_STREAMS) <= $self->{active_peer_streams} ) { tracer->warning("SETTINGS_MAX_CONCURRENT_STREAMS exceeded\n"); $self->stream_error( $stream_id, REFUSED_STREAM ); return undef; } $self->{active_peer_streams}++; tracer->debug("Active streams: $self->{active_peer_streams}"); $self->{streams}->{$stream_id} = { 'state' => IDLE, 'weight' => DEFAULT_WEIGHT, 'stream_dep' => 0, 'fcw_recv' => $self->dec_setting(SETTINGS_INITIAL_WINDOW_SIZE), 'fcw_send' => $self->enc_setting(SETTINGS_INITIAL_WINDOW_SIZE), }; $self->{on_new_peer_stream}->($stream_id) if exists $self->{on_new_peer_stream}; return $self->{last_peer_stream}; } sub stream { my ( $self, $stream_id ) = @_; return undef unless exists $self->{streams}->{$stream_id}; $self->{streams}->{$stream_id}; } # stream_state ( $self, $stream_id, $new_state?, $pending? ) sub stream_state { my $self = shift; my $stream_id = shift; return undef unless exists $self->{streams}->{$stream_id}; my $s = $self->{streams}->{$stream_id}; if (@_) { my ( $new_state, $pending ) = @_; if ($pending) { $self->stream_pending_state( $stream_id, $new_state ); } else { $self->{on_change_state}->( $stream_id, $s->{state}, $new_state ) if exists $self->{on_change_state}; $s->{state} = $new_state; # Exec callbacks for new state if ( exists $s->{cb} && exists $s->{cb}->{ $s->{state} } ) { for my $cb ( @{ $s->{cb}->{ $s->{state} } } ) { $cb->(); } } # Cleanup if ( $new_state == CLOSED ) { $self->{active_peer_streams}-- if $self->{active_peer_streams} && ( ( $stream_id % 2 ) ^ ( $self->{type} == CLIENT ) ); tracer->info( "Active streams: $self->{active_peer_streams} $stream_id"); for my $key ( keys %$s ) { next if grep { $key eq $_ } ( qw(state weight stream_dep fcw_recv fcw_send reset) ); delete $s->{$key}; } } } } $s->{state}; } sub stream_pending_state { my $self = shift; my $stream_id = shift; return undef unless exists $self->{streams}->{$stream_id}; my $s = $self->{streams}->{$stream_id}; if (@_) { $s->{pending_state} = shift; $self->{pending_stream} = defined $s->{pending_state} ? $stream_id : undef; } $s->{pending_state}; } sub stream_cb { my ( $self, $stream_id, $state, $cb ) = @_; return undef unless exists $self->{streams}->{$stream_id}; push @{ $self->{streams}->{$stream_id}->{cb}->{$state} }, $cb; } sub stream_frame_cb { my ( $self, $stream_id, $frame, $cb ) = @_; return undef unless exists $self->{streams}->{$stream_id}; push @{ $self->{streams}->{$stream_id}->{frame_cb}->{$frame} }, $cb; } sub stream_data { my $self = shift; my $stream_id = shift; return undef unless exists $self->{streams}->{$stream_id}; my $s = $self->{streams}->{$stream_id}; if (@_) { # Exec callbacks for data if ( exists $s->{frame_cb} && exists $s->{frame_cb}->{&DATA} ) { for my $cb ( @{ $s->{frame_cb}->{&DATA} } ) { $cb->( $_[0] ); } } else { $s->{data} .= shift; } } $s->{data}; } sub stream_headers_done { my $self = shift; my $stream_id = shift; return undef unless exists $self->{streams}->{$stream_id}; my $s = $self->{streams}->{$stream_id}; my $res = headers_decode( $self, \$s->{header_block}, 0, length $s->{header_block}, $stream_id ); tracer->debug("Headers done for stream $stream_id\n"); return undef unless defined $res; # Clear header_block $s->{header_block} = ''; my $eh = $self->decode_context->{emitted_headers}; my $is_response = $self->{type} == CLIENT && !$s->{promised_sid}; my $is_trailer = !!$self->stream_trailer($stream_id); return undef unless $self->validate_headers( $eh, $stream_id, $is_response ); if ( $s->{promised_sid} ) { $self->{streams}->{ $s->{promised_sid} }->{pp_headers} = $eh; } elsif ($is_trailer) { $self->stream_trailer_headers( $stream_id, $eh ); } else { $s->{headers} = $eh; } # Exec callbacks for headers if ( exists $s->{frame_cb} && exists $s->{frame_cb}->{&HEADERS} ) { for my $cb ( @{ $s->{frame_cb}->{&HEADERS} } ) { $cb->($eh); } } # Clear emitted headers $self->decode_context->{emitted_headers} = []; return 1; } sub validate_headers { my ( $self, $headers, $stream_id, $is_response ) = @_; my $pseudo_flag = 1; my %pseudo_hash = (); my @h = $is_response ? (qw(:status)) : ( qw(:method :scheme :authority :path) ); # Trailer headers ? if ( my $t = $self->stream_trailer($stream_id) ) { for my $i ( 0 .. @$headers / 2 - 1 ) { my ( $h, $v ) = ( $headers->[ $i * 2 ], $headers->[ $i * 2 + 1 ] ); if ( !exists $t->{$h} ) { tracer->warning( "header <$h> doesn't listed in the trailer header"); $self->stream_error( $stream_id, PROTOCOL_ERROR ); return undef; } } return 1; } for my $i ( 0 .. @$headers / 2 - 1 ) { my ( $h, $v ) = ( $headers->[ $i * 2 ], $headers->[ $i * 2 + 1 ] ); if ( $h =~ /^\:/ ) { if ( !$pseudo_flag ) { tracer->warning( "pseudo-header <$h> appears after a regular header"); $self->stream_error( $stream_id, PROTOCOL_ERROR ); return undef; } elsif ( !grep { $_ eq $h } @h ) { tracer->warning("invalid pseudo-header <$h>"); $self->stream_error( $stream_id, PROTOCOL_ERROR ); return undef; } elsif ( exists $pseudo_hash{$h} ) { tracer->warning("repeated pseudo-header <$h>"); $self->stream_error( $stream_id, PROTOCOL_ERROR ); return undef; } $pseudo_hash{$h} = $v; next; } $pseudo_flag = 0 if $pseudo_flag; if ( $h eq 'connection' ) { tracer->warning("connection header is not valid in http/2"); $self->stream_error( $stream_id, PROTOCOL_ERROR ); return undef; } elsif ( $h eq 'te' && $v ne 'trailers' ) { tracer->warning("TE header can contain only value 'trailers'"); $self->stream_error( $stream_id, PROTOCOL_ERROR ); return undef; } elsif ( $h eq 'content-length' ) { $self->stream_length( $stream_id, $v ); } elsif ( $h eq 'trailer' ) { my %th = map { $_ => 1 } split /\s*,\s*/, lc($v); if ( grep { exists $th{$_} } ( qw(transfer-encoding content-length host authentication cache-control expect max-forwards pragma range te content-encoding content-type content-range trailer) ) ) { tracer->warning("trailer header contain forbidden headers"); $self->stream_error( $stream_id, PROTOCOL_ERROR ); return undef; } $self->stream_trailer( $stream_id, {%th} ); } } for my $h (@h) { next if exists $pseudo_hash{$h}; tracer->warning("missed mandatory pseudo-header $h"); $self->stream_error( $stream_id, PROTOCOL_ERROR ); return undef; } 1; } # RST_STREAM for stream errors sub stream_error { my ( $self, $stream_id, $error ) = @_; $self->enqueue( RST_STREAM, 0, $stream_id, $error ); } # Flow control windown of stream sub _stream_fcw { my $dir = shift; my $self = shift; my $stream_id = shift; return undef unless exists $self->{streams}->{$stream_id}; my $s = $self->{streams}->{$stream_id}; if (@_) { $s->{$dir} += shift; tracer->debug( "Stream $stream_id $dir now is " . $s->{$dir} . "\n" ); } $s->{$dir}; } sub stream_fcw_send { _stream_fcw( 'fcw_send', @_ ); } sub stream_fcw_recv { _stream_fcw( 'fcw_recv', @_ ); } sub stream_fcw_update { my ( $self, $stream_id ) = @_; # TODO: check size of data of stream in memory my $size = $self->dec_setting(SETTINGS_INITIAL_WINDOW_SIZE); tracer->debug("update fcw recv of stream $stream_id with $size b.\n"); $self->stream_fcw_recv( $stream_id, $size ); $self->enqueue( WINDOW_UPDATE, 0, $stream_id, $size ); } sub stream_send_blocked { my ( $self, $stream_id ) = @_; my $s = $self->{streams}->{$stream_id} or return undef; if ( length( $s->{blocked_data} ) && $self->stream_fcw_send($stream_id) > 0 ) { $self->send_data($stream_id); } } sub stream_reprio { my ( $self, $stream_id, $exclusive, $stream_dep ) = @_; return undef unless exists $self->{streams}->{$stream_id} && ( $stream_dep == 0 || exists $self->{streams}->{$stream_dep} ) && $stream_id != $stream_dep; my $s = $self->{streams}; if ( $s->{$stream_id}->{stream_dep} != $stream_dep ) { # check if new stream_dep is stream child if ( $stream_dep != 0 ) { my $sid = $stream_dep; while ( $sid = $s->{$sid}->{stream_dep} ) { next unless $sid == $stream_id; # Child take my stream dep $s->{$stream_dep}->{stream_dep} = $s->{$stream_id}->{stream_dep}; last; } } # Set new stream dep $s->{$stream_id}->{stream_dep} = $stream_dep; } if ($exclusive) { # move all siblings to childs for my $sid ( keys %$s ) { next if $s->{$sid}->{stream_dep} != $stream_dep || $sid == $stream_id; $s->{$sid}->{stream_dep} = $stream_id; } } return 1; } 1; Trace.pm100644000764000764 317013562447335 20132 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/lib/Protocol/HTTP2package Protocol::HTTP2::Trace; use strict; use warnings; use Time::HiRes qw(time); use Exporter qw(import); our @EXPORT_OK = qw(tracer bin2hex); my %levels = ( debug => 0, info => 1, notice => 2, warning => 3, error => 4, critical => 5, alert => 6, emergency => 7, ); my $tracer_sngl = Protocol::HTTP2::Trace->_new( min_level => ( exists $ENV{HTTP2_DEBUG} && exists $levels{ $ENV{HTTP2_DEBUG} } ) ? $levels{ $ENV{HTTP2_DEBUG} } : $levels{error} ); my $start_time = 0; sub tracer { $tracer_sngl; } sub _new { my ( $class, %opts ) = @_; bless {%opts}, $class; } sub _log { my ( $self, $level, $message ) = @_; $level = uc($level); chomp($message); my $now = time; if ( $now - $start_time < 60 ) { $message =~ s/\n/\n /g; printf "[%05.3f] %s %s\n", $now - $start_time, $level, $message; } else { my @t = ( localtime() )[ 5, 4, 3, 2, 1, 0 ]; $t[0] += 1900; $t[1]++; $message =~ s/\n/\n /g; printf "[%4d-%02d-%02d %02d:%02d:%02d] %s %s\n", @t, $level, $message; $start_time = $now; } } { no strict 'refs'; for my $l ( keys %levels ) { *{ __PACKAGE__ . "::" . $l } = ( $levels{$l} >= $tracer_sngl->{min_level} ) ? sub { shift->_log( $l, @_ ); } : sub { 1 } } } sub bin2hex { my $bin = shift; my $c = 0; my $s; join "", map { $c++; $s = !( $c % 16 ) ? "\n" : ( $c % 2 ) ? "" : " "; $_ . $s } unpack( "(H2)*", $bin ); } 1; Upgrade.pm100644000764000764 704613562447335 20471 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/lib/Protocol/HTTP2package Protocol::HTTP2::Upgrade; use strict; use warnings; use Protocol::HTTP2; use Protocol::HTTP2::Constants qw(:frame_types :errors :states); use Protocol::HTTP2::Trace qw(tracer); use MIME::Base64 qw(encode_base64url decode_base64url); #use re 'debug'; my $end_headers_re = qr/\G.+?\x0d?\x0a\x0d?\x0a/s; my $header_re = qr/\G[ \t]*(.+?)[ \t]*\:[ \t]*(.+?)[ \t]*\x0d?\x0a/; sub upgrade_request { my ( $con, %h ) = @_; my $request = sprintf "%s %s HTTP/1.1\x0d\x0aHost: %s\x0d\x0a", $h{':method'}, $h{':path'}, $h{':authority'}; while ( my ( $h, $v ) = splice( @{ $h{headers} }, 0, 2 ) ) { next if grep { lc($h) eq $_ } (qw(connection upgrade http2-settings)); $request .= $h . ': ' . $v . "\x0d\x0a"; } $request .= join "\x0d\x0a", 'Connection: Upgrade, HTTP2-Settings', 'Upgrade: ' . Protocol::HTTP2::ident_plain, 'HTTP2-Settings: ' . encode_base64url( $con->frame_encode( SETTINGS, 0, 0, {} ) ), '', ''; } sub upgrade_response { join "\x0d\x0a", "HTTP/1.1 101 Switching Protocols", "Connection: Upgrade", "Upgrade: " . Protocol::HTTP2::ident_plain, "", ""; } sub decode_upgrade_request { my ( $con, $buf_ref, $buf_offset, $headers_ref ) = @_; pos($$buf_ref) = $buf_offset; # Search end of headers return 0 if $$buf_ref !~ /$end_headers_re/g; my $end_headers_pos = pos($$buf_ref) - $buf_offset; pos($$buf_ref) = $buf_offset; # Request return undef if $$buf_ref !~ m#\G(\w+) ([^ ]+) HTTP/1\.1\x0d?\x0a#g; my ( $method, $uri ) = ( $1, $2 ); # TODO: remove after http2 -> http/1.1 headers conversion implemented push @$headers_ref, ":method", $method; push @$headers_ref, ":path", $uri; push @$headers_ref, ":scheme", 'http'; my $success = 0; # Parse headers while ( $success != 0b111 && $$buf_ref =~ /$header_re/gc ) { my ( $header, $value ) = ( lc($1), $2 ); if ( $header eq "connection" ) { my %h = map { $_ => 1 } split /\s*,\s*/, lc($value); $success |= 0b001 if exists $h{'upgrade'} && exists $h{'http2-settings'}; } elsif ( $header eq "upgrade" && grep { $_ eq Protocol::HTTP2::ident_plain } split /\s*,\s*/, $value ) { $success |= 0b010; } elsif ( $header eq "http2-settings" && defined $con->frame_decode( \decode_base64url($value), 0 ) ) { $success |= 0b100; } else { push @$headers_ref, $header, $value; } } return undef unless $success == 0b111; # TODO: method POST also can contain data... return $end_headers_pos; } sub decode_upgrade_response { my ( $con, $buf_ref, $buf_offset ) = @_; pos($$buf_ref) = $buf_offset; # Search end of headers return 0 if $$buf_ref !~ /$end_headers_re/g; my $end_headers_pos = pos($$buf_ref) - $buf_offset; pos($$buf_ref) = $buf_offset; # Switch Protocols failed return undef if $$buf_ref !~ m#\GHTTP/1\.1 101 .+?\x0d?\x0a#g; my $success = 0; # Parse headers while ( $success != 0b11 && $$buf_ref =~ /$header_re/gc ) { my ( $header, $value ) = ( lc($1), $2 ); if ( $header eq "connection" && lc($value) eq "upgrade" ) { $success |= 0b01; } elsif ( $header eq "upgrade" && $value eq Protocol::HTTP2::ident_plain ) { $success |= 0b10; } } return undef unless $success == 0b11; return $end_headers_pos; } 1; minil.toml100644000764000764 11513562447335 15307 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10name = "Protocol-HTTP2" module_maker="ModuleBuildTiny" # badges = ["travis"] 00_compile.t100644000764000764 12413562447335 15661 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/tuse strict; use Test::More; use_ok $_ for qw( Protocol::HTTP2 ); done_testing; 01_HeaderCompression.t100644000764000764 1774213562447335 17722 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/tuse strict; use warnings; use Test::More; use lib 't/lib'; use PH2Test; use Protocol::HTTP2::Connection; use Protocol::HTTP2::Constants qw(:endpoints :limits :settings); BEGIN { use_ok( 'Protocol::HTTP2::HeaderCompression', qw(int_encode int_decode str_encode str_decode headers_decode headers_encode) ); } subtest 'int_encode' => sub { ok binary_eq( int_encode( 0, 8 ), pack( "C", 0 ) ); ok binary_eq( int_encode( 0xFD, 8 ), pack( "C", 0xFD ) ); ok binary_eq( int_encode( 0xFF, 8 ), pack( "C*", 0xFF, 0x00 ) ); ok binary_eq( int_encode( 0x100, 8 ), pack( "C*", 0xFF, 0x01 ) ); ok binary_eq( int_encode( 1337, 5 ), pack( "C*", 31, 154, 10 ) ); }; subtest 'int_decode' => sub { my $buf = pack( "C*", 31, 154, 10 ); my $int = 0; is int_decode( \$buf, 0, \$int, 5 ), 3; is $int, 1337; }; subtest 'str_encode' => sub { ok binary_eq( str_encode('//ee'), hstr("8361 8297") ); }; subtest 'str_decode' => sub { my $s = hstr(< sub { my $con = Protocol::HTTP2::Connection->new(CLIENT); my $ctx = $con->encode_context; ok binary_eq( headers_encode( $ctx, [ ':method' => 'GET', ':scheme' => 'http', ':path' => '/', ':authority' => 'www.example.com', ] ), hstr(< 'GET', ':scheme' => 'http', ':path' => '/', ':authority' => 'www.example.com', 'cache-control' => 'no-cache', ] ), hstr(< 'GET', ':scheme' => 'https', ':path' => '/index.html', ':authority' => 'www.example.com', 'custom-key' => 'custom-value', ] ), hstr(< sub { my $con = Protocol::HTTP2::Connection->new(SERVER); my $ctx = $con->encode_context; $ctx->{max_ht_size} = $ctx->{settings}->{&SETTINGS_HEADER_TABLE_SIZE} = 256; ok binary_eq( headers_encode( $ctx, [ ':status' => '302', 'cache-control' => 'private', 'date' => 'Mon, 21 Oct 2013 20:13:21 GMT', 'location' => 'https://www.example.com', ] ), hstr(<{ht_size} => 222, 'ht_size ok'; ok binary_eq( headers_encode( $ctx, [ ':status' => 307, 'cache-control' => 'private', 'date' => 'Mon, 21 Oct 2013 20:13:21 GMT', 'location' => 'https://www.example.com', ] ), hstr("4803 3330 37c1 c0bf") ); is $ctx->{ht_size} => 222, 'ht_size ok'; ok binary_eq( headers_encode( $ctx, [ ':status' => 200, 'cache-control' => 'private', 'date' => 'Mon, 21 Oct 2013 20:13:22 GMT', 'location' => 'https://www.example.com', 'content-encoding' => 'gzip', 'set-cookie' => 'foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1', ] ), hstr(<{ht_size} => 215, 'ht_size ok'; }; subtest 'decode requests' => sub { my $con = Protocol::HTTP2::Connection->new(SERVER); my $ctx = $con->decode_context; my $buf = hstr(<{emitted_headers}, [ ':method' => 'GET', ':scheme' => 'http', ':path' => '/', ':authority' => 'www.example.com' ], "emitted headers"; $ctx->{emitted_headers} = []; is_deeply $ctx->{header_table}, [ [ ':authority' => 'www.example.com' ] ], "dynamic table"; is $ctx->{ht_size}, 57, "correct table size"; $buf = hstr('8286 84be 5886 a8eb 1064 9cbf'); is headers_decode( $con, \$buf, 0, length $buf ), length($buf), "correct offset"; is_deeply $ctx->{emitted_headers}, [ ':method' => 'GET', ':scheme' => 'http', ':path' => '/', ':authority' => 'www.example.com', 'cache-control' => 'no-cache', ], "emitted headers"; $ctx->{emitted_headers} = []; is_deeply $ctx->{header_table}, [ [ 'cache-control' => 'no-cache' ], [ ':authority' => 'www.example.com' ] ], "dynamic table"; is $ctx->{ht_size}, 110, "correct table size"; $buf = hstr(<{emitted_headers}, [ ':method' => 'GET', ':scheme' => 'https', ':path' => '/index.html', ':authority' => 'www.example.com', 'custom-key' => 'custom-value', ], "emitted headers"; is_deeply $ctx->{header_table}, [ [ 'custom-key' => 'custom-value' ], [ 'cache-control' => 'no-cache' ], [ ':authority' => 'www.example.com' ] ], "dynamic table"; is $ctx->{ht_size}, 164, "correct table size"; }; done_testing(); __END__ subtest 'decode responses' => sub { my $decoder = Protocol::HTTP2::HeaderCompression->new; $decoder->{_max_ht_size} = 256; is $decoder->headers_decode( \hstr(< '302' ], [ 'cache-control' => 'private' ], [ 'date' => 'Mon, 21 Oct 2013 20:13:21 GMT' ], [ 'location' => 'https://www.example.com' ], ] or diag explain \@headers; @headers = (); is $decoder->headers_decode( \hstr("8c"), \@headers ), 1; is_deeply \@headers, [ [ ':status' => '200' ], ] or diag explain \@headers; @headers = (); is $decoder->headers_decode( \hstr(< 'private' ], [ 'date' => 'Mon, 21 Oct 2013 20:13:22 GMT' ], [ 'content-encoding' => 'gzip' ], [ 'location' => 'https://www.example.com' ], [ ':status' => '200' ], [ 'set-cookie' => 'foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1' ], ] or diag explain \@headers; }; done_testing; 02_Huffman.t100644000764000764 52413562447335 15623 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/tuse strict; use warnings; use Test::More; use Data::Dumper; BEGIN { use_ok('Protocol::HTTP2::Huffman') } use lib 't/lib'; use PH2Test; my $example = "www.example.com"; my $s = huffman_encode($example); ok binary_eq( $s, hstr("f1e3 c2e5 f23a 6ba0 ab90 f4ff") ), "encode"; is huffman_decode($s), $example, "decode"; done_testing(); 03_connection.t100644000764000764 1015113562447335 16434 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/tuse strict; use warnings; use Test::More; use Protocol::HTTP2::Constants qw(const_name :endpoints :states); use lib 't/lib'; use PH2Test; BEGIN { use_ok('Protocol::HTTP2::Connection'); } done_testing(); __END__ subtest 'decode_request' => sub { my $data = hstr(<new( SERVER, on_change_state => sub { my ( $stream_id, $previous_state, $current_state ) = @_; printf "Stream %i changed state from %s to %s\n", $stream_id, const_name( "states", $previous_state ), const_name( "states", $current_state ); if ( $current_state == HALF_CLOSED ) { $run_test->($stream_id); } }, on_error => sub { fail("Error occured"); } ); my $run_test_flag = 0; $run_test = sub { my $stream_id = shift; is_deeply( $con->stream_headers($stream_id), [ ':authority' => '127.0.0.1:8000', ':method' => 'GET', ':path' => '/LICENSE', ':scheme' => 'http', 'accept' => '*/*', 'accept-encoding' => 'gzip, deflate', 'user-agent' => 'nghttp2/0.4.0-DEV', ], "correct request headers" ) and $run_test_flag = 1; }; my $offset = $con->preface_decode( \$data, 0 ); is( $offset, 24, "Preface exists" ) or BAIL_OUT "preface?"; while ( my $size = $con->frame_decode( \$data, $offset ) ) { $offset += $size; } ok( $con->error == 0 && $run_test_flag, "decode headers" ); $data = hstr("0000 0401 0000 0000"); $offset = 0; while ( my $size = $con->frame_decode( \$data, $offset ) ) { $offset += $size; } ok( $con->error == 0 ); $data = hstr("0008 0700 0000 0000 0000 0000 0000 0000"); $offset = 0; while ( my $size = $con->frame_decode( \$data, $offset ) ) { $offset += $size; } ok( $con->error == 0 ); }; subtest 'decode_response' => sub { my $data = hstr(<new(CLIENT); # Emulate request my $sid = $con->new_stream; $con->stream_state( $sid, HALF_CLOSED ); my $run_test_flag = 0; $con->stream_cb( $sid, CLOSED, sub { is_deeply( $con->stream_headers($sid), [ ':status' => 200, 'server' => 'nghttpd nghttp2/0.4.0-DEV', 'content-length' => 46, 'cache-control' => 'max-age=3600', 'date' => 'Fri, 18 Apr 2014 07:27:11 GMT', 'last-modified' => 'Thu, 27 Feb 2014 10:30:37 GMT', ], "correct response headers" ) and $run_test_flag = 1; } ); my $offset = 0; while ( my $size = $con->frame_decode( \$data, $offset ) ) { $offset += $size; } ok( $con->error == 0 ); $data = hstr(<frame_decode( \$data, $offset ) ) { $offset += $size; } is $offset, length($data), "read all data"; ok( $con->error == 0 && $run_test_flag ); }; done_testing(); 04_continuation.t100644000764000764 201513562447335 16770 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/tuse strict; use warnings; use Test::More; use Protocol::HTTP2::Constants qw(const_name :endpoints :states); BEGIN { use_ok('Protocol::HTTP2::Connection'); } done_testing; __END__ subtest 'decode_continuation_request' => sub { open my $fh, '<:raw', 't/continuation.request.data' or die $!; my $data = do { local $/; <$fh> }; my $con = Protocol::HTTP2::Connection->new( SERVER, on_change_state => sub { my ( $stream_id, $previous_state, $current_state ) = @_; printf "Stream %i changed state from %s to %s\n", $stream_id, const_name( "states", $previous_state ), const_name( "states", $current_state ); }, on_error => sub { fail("Error occured"); } ); my $offset = $con->preface_decode( \$data, 0 ); is( $offset, 24, "Preface exists" ) or BAIL_OUT "preface?"; while ( my $size = $con->frame_decode( \$data, $offset ) ) { $offset += $size; } is $con->error, 0, "no errors"; }; done_testing; 05_trace.t100644000764000764 37613562447335 15345 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/tuse strict; use warnings; use Test::More; BEGIN { use_ok( 'Protocol::HTTP2::Trace', qw(tracer bin2hex) ); } subtest 'bin2hex' => sub { is bin2hex("ABCDEFGHIJKLMNOPQR"), "4142 4344 4546 4748 494a 4b4c 4d4e 4f50\n5152 "; }; done_testing; 06_upgrade.t100644000764000764 254113562447335 15713 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/tuse strict; use warnings; use Test::More; use Protocol::HTTP2::Constants qw(const_name :endpoints :states); BEGIN { use_ok('Protocol::HTTP2::Connection'); } subtest 'decode_upgrade_response' => sub { my $con = Protocol::HTTP2::Connection->new(CLIENT); my $buf = join "\x0d\x0a", "\x00HTTP/1.1 101 Switching Protocols", "Connection: Upgrade", "SomeHeader: bla bla", "Upgrade: h2c", "", "here is some binary data"; is $con->decode_upgrade_response( \$buf, 1 ), 92, "correct pos"; $buf =~ s/101/200/; is $con->decode_upgrade_response( \$buf, 1 ), undef, "no switch"; $buf =~ s/200/101/; $buf =~ s/h2c/xyz/; is $con->decode_upgrade_response( \$buf, 1 ), undef, "wrong Upgrade protocol"; $buf =~ s/xyz/h2c/; is $con->decode_upgrade_response( \substr( $buf, 0, 80 ), 1 ), 0, "wait another portion of data\n"; }; subtest 'decode_upgrade_request' => sub { my $con = Protocol::HTTP2::Connection->new(SERVER); my $buf = join "\x0d\x0a", "\x00GET /default.htm HTTP/1.1", "Host: server.example.com", "Connection: Upgrade, HTTP2-Settings", "Upgrade: h2c", "HTTP2-Settings: AAAABAAAAAAA", "User-Agent: perl-Protocol-HTTP2/0.10", "", ""; is $con->decode_upgrade_request( \$buf, 1 ), length($buf) - 1, "correct pos"; }; done_testing; 07_ping.t100644000764000764 351413562447335 15223 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/tuse strict; use warnings; use Test::More; use lib 't/lib'; use PH2Test; use Protocol::HTTP2::Constants qw(const_name :frame_types :endpoints :states :flags); use Protocol::HTTP2::Connection; use Protocol::HTTP2::Client; use Protocol::HTTP2::Server; subtest 'ping' => sub { my $client = Protocol::HTTP2::Client->new; $client->request( ':authority' => 'localhost', ':method' => 'GET', ':path' => '/', ':scheme' => 'https', ); my $server = Protocol::HTTP2::Server->new; while ( my $frame = $client->next_frame ) { $server->feed($frame); while ( $frame = $server->next_frame ) { $client->feed($frame); } } $client->ping("HELLOSRV"); my $ping = $client->next_frame; ok binary_eq( $ping, hstr("0000 0806 0000 0000 0048 454c 4c4f 5352 56") ), "ping"; $server->feed($ping); ok binary_eq( $ping = $server->next_frame, hstr("0000 0806 0100 0000 0048 454c 4c4f 5352 56") ), "ping ack"; is $server->next_frame, undef; $client->feed($ping); is $client->next_frame, undef; }; subtest 'dont mess with continuation' => sub { my $con = Protocol::HTTP2::Connection->new(CLIENT); $con->preface(1); $con->new_stream(1); my @hdrs = ( HEADERS, 0, 1, { hblock => \"\x82" } ); my @cont = ( CONTINUATION, END_HEADERS, 1, \"\x85" ); my @data = ( DATA, 0, 1, \"DATA" ); $con->enqueue( @hdrs, @cont, @data ); ok binary_eq( $con->dequeue, $con->frame_encode(@hdrs) ), "1-HEADER"; my @ping = ( PING, 0, 0, \"HELLOSRV" ); $con->enqueue_first(@ping); ok binary_eq( $con->dequeue, $con->frame_encode(@cont) ), "2-CONTINUATION"; ok binary_eq( $con->dequeue, $con->frame_encode(@ping) ), "3-PING"; ok binary_eq( $con->dequeue, $con->frame_encode(@data) ), "4-DATA"; }; done_testing 08_priority.t100644000764000764 507313562447335 16152 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/tuse strict; use warnings; use Test::More; use lib 't/lib'; use PH2Test; use Protocol::HTTP2::Constants qw(const_name :frame_types :endpoints :states :flags); use Protocol::HTTP2::Connection; subtest 'priority frame' => sub { my $con = Protocol::HTTP2::Connection->new(SERVER); my $frame = $con->frame_encode( PRIORITY, 0, 1, [ 0, 32 ] ); ok binary_eq( $frame, hstr("0000 0502 0000 0000 0100 0000 001f") ), "PRIORITY"; # Simulate client request $con->preface(1); $con->new_peer_stream(1); my $res = $con->frame_decode( \$frame, 0 ); is $res, 14, "decoded correctly"; $frame = $con->frame_encode( HEADERS, END_HEADERS, 1, { hblock => \hstr("41 8aa0 e41d 139d 09b8 f000 0f82 8486"), stream_dep => 0, weight => 32 } ); ok binary_eq( $frame, hstr( "0000 1401 2400 0000 0100 0000 001f 418a" . "a0e4 1d13 9d09 b8f0 000f 8284 86" ) ), "Headers with priority"; $res = $con->frame_decode( \$frame, 0 ); is $res, 29, "decoded correctly"; }; subtest 'stream reprioritization' => sub { my $con = Protocol::HTTP2::Connection->new(SERVER); # Simulate client request $con->preface(1); $con->new_peer_stream(1); $con->new_peer_stream(3); $con->new_peer_stream(5); # 0 # /|\ # 1 3 5 ok $con->stream_reprio( 1, 1, 0 ), "stream_reprio exclusive 1 done"; # 1 # / \ # 3 5 is $con->stream(1)->{stream_dep}, 0, "1 on top"; is $con->stream(3)->{stream_dep}, 1, "3 under 1"; is $con->stream(5)->{stream_dep}, 1, "5 under 1"; $con->new_peer_stream(7); ok $con->stream_reprio( 7, 0, 1 ), "stream_reprio 7"; $con->new_peer_stream(9); ok $con->stream_reprio( 9, 0, 7 ), "stream_reprio 9"; $con->new_peer_stream(11); ok $con->stream_reprio( 11, 0, 9 ), "stream_reprio 11"; ok $con->stream_reprio( 1, 0, 9 ), "stream_reprio 1 under 9"; # # 1 9 # / | \ / \ # 3 5 7 11 1 # \ => / | \ # 9 3 5 7 # \ # 11 is $con->stream(9)->{stream_dep}, 0, "9 on top"; is $con->stream(11)->{stream_dep}, 9, "11 under 9"; is $con->stream(1)->{stream_dep}, 9, "1 under 9"; is $con->stream(3)->{stream_dep}, 1, "3 under 1"; is $con->stream(5)->{stream_dep}, 1, "3 under 1"; is $con->stream(7)->{stream_dep}, 1, "7 under 1"; #diag explain $con->{streams}; }; done_testing; 09_client_server_tcp.t100644000764000764 1016313562447335 20020 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/tuse strict; use warnings; use Test::More; use lib 't/lib'; use PH2ClientServerTest; use Test::TCP; use Protocol::HTTP2; my $host = '127.0.0.1'; subtest 'client/server' => sub { for my $opts ( [ "without tls", [], [] ], [ "without tls, upgrade", [ upgrade => 1 ], [ upgrade => 1 ] ], [ "tls/npn", [ npn => 1 ], [ npn => 1, tls_crt => 'examples/test.crt', tls_key => 'examples/test.key' ] ], [ "tls/alpn", [ alpn => 1 ], [ alpn => 1, tls_crt => 'examples/test.crt', tls_key => 'examples/test.key' ] ], ) { my $test = shift @$opts; note "test: $test\n"; # Check for NPN/ALPN if ( !check_tls( @{ $opts->[0] } ) ) { note "skiped $test: feature not avaliable\n"; next; } eval { local $SIG{ALRM} = sub { die "timeout\n" }; alarm 16; test_tcp( client => sub { my $port = shift; client( @{ $opts->[0] }, port => $port, host => $host, on_error => sub { fail "error occured: " . shift; }, test_cb => sub { my $client = shift; $client->request( ':scheme' => "http", ':authority' => $host . ":" . $port, ':path' => "/", ':method' => "GET", headers => [ 'accept' => '*/*', 'user-agent' => 'perl-Protocol-HTTP2/' . $Protocol::HTTP2::VERSION, ], on_done => sub { my ( $headers, $data ) = @_; is scalar(@$headers) / 2, 6, "get response headers"; is length($data), 13, "get body"; }, ); } ); }, server => sub { my $port = shift; my $server; server( @{ $opts->[1] }, port => $port, host => $host, on_error => sub { fail "error occured: " . shift; }, test_cb => sub { $server = shift; }, on_request => sub { my ( $stream_id, $headers, $data ) = @_; my $message = "hello, world!"; $server->response( ':status' => 200, stream_id => $stream_id, headers => [ 'server' => 'perl-Protocol-HTTP2/' . $Protocol::HTTP2::VERSION, 'content-length' => length($message), 'cache-control' => 'max-age=3600', 'date' => 'Fri, 18 Apr 2014 07:27:11 GMT', 'last-modified' => 'Thu, 27 Feb 2014 10:30:37 GMT', ], data => $message, ); }, ); }, ); alarm 0; }; is $@, '', "no errors"; } }; done_testing; 10_settings.t100644000764000764 160013562447335 16112 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/tuse strict; use warnings; use Test::More; use lib 't/lib'; use PH2Test; use Protocol::HTTP2::Constants qw(:settings); use Protocol::HTTP2::Client; use Protocol::HTTP2::Server; subtest 'client settings' => sub { my $c = Protocol::HTTP2::Client->new( settings => { &SETTINGS_HEADER_TABLE_SIZE => 100 } ); $c->request( ':scheme' => 'http', ':authority' => 'localhost:8000', ':path' => '/', ':method' => 'GET', ); # PRI $c->next_frame; # SETTINGS ok binary_eq( hstr('0000 0604 0000 0000 0000 0100 0000 64'), $c->next_frame ), "send only changed from default values settings"; }; subtest 'server settings' => sub { my $s = Protocol::HTTP2::Server->new; ok binary_eq( hstr('0000 0604 0000 0000 0000 0300 0000 64'), $s->next_frame ), "server defaults not empty"; }; done_testing; 11_server_stream.t100644000764000764 605513562447335 17145 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/tuse strict; use warnings; use Test::More; use Protocol::HTTP2::Client; use Protocol::HTTP2::Server; use lib 't/lib'; use PH2Test qw(fake_connect); subtest 'server stream' => sub { my $server; $server = Protocol::HTTP2::Server->new( on_request => sub { my ( $stream_id, $headers, $data ) = @_; my $s_stream = $server->response_stream( ':status' => 200, stream_id => $stream_id, # HTTP/1.1 Headers headers => [ 'server' => 'perl-Protocol-HTTP2/0.16', 'cache-control' => 'max-age=3600', 'date' => 'Fri, 18 Apr 2014 07:27:11 GMT', 'last-modified' => 'Thu, 27 Feb 2014 10:30:37 GMT', ], ); isa_ok( $s_stream, 'Protocol::HTTP2::Server::Stream' ); $s_stream->send('a'); $s_stream->send('b'); $s_stream->close; }, ); my $client = Protocol::HTTP2::Client->new; $client->request( # HTTP/2 headers ':scheme' => 'http', ':authority' => 'localhost:8000', ':path' => '/', ':method' => 'GET', # HTTP/1.1 headers headers => [ 'accept' => '*/*', 'user-agent' => 'perl-Protocol-HTTP2/0.16', ], # Callback when receive server's response on_done => sub { my ( $headers, $data ) = @_; is $data, "ab", "stream data ok"; }, ); fake_connect( $server, $client ); }; subtest 'client cancel' => sub { my $cancel = 0; my $s_stream; my $server; $server = Protocol::HTTP2::Server->new( on_request => sub { my ( $stream_id, $headers, $data ) = @_; $s_stream = $server->response_stream( ':status' => 200, stream_id => $stream_id, # HTTP/1.1 Headers headers => [ 'server' => 'perl-Protocol-HTTP2/0.16', ], on_cancel => sub { $cancel = 1; } ); isa_ok( $s_stream, 'Protocol::HTTP2::Server::Stream' ); $s_stream->send('a'); }, ); my $client = Protocol::HTTP2::Client->new; $client->request( # HTTP/2 headers ':scheme' => 'http', ':authority' => 'localhost:8000', ':path' => '/', ':method' => 'GET', # HTTP/1.1 headers headers => [ 'user-agent' => 'perl-Protocol-HTTP2/0.16', ], on_headers => sub { is_deeply $_[0], [ ':status' => 200, 'server' => 'perl-Protocol-HTTP2/0.16' ], "correct headers"; 1; }, # Callback when receive server's response on_data => sub { my ( $chunk, $headers ) = @_; is $chunk, "a", "stream data ok"; # cancel 0; }, ); fake_connect( $server, $client ); is $cancel, 1, "successfully canceled"; }; done_testing; 12_leaks.t100644000764000764 623513562447335 15364 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/tuse strict; use warnings; use Test::More; use Protocol::HTTP2::Client; use Protocol::HTTP2::Server; use Test::LeakTrace; sub fake_connect { my ( $server, $client ) = @_; my ( $clt_frame, $srv_frame ); do { $clt_frame = $client->next_frame; $srv_frame = $server->next_frame; $server->feed($clt_frame) if $clt_frame; $client->feed($srv_frame) if $srv_frame; } while ( $clt_frame || $srv_frame ); } no_leaks_ok { my $server; $server = Protocol::HTTP2::Server->new( on_request => sub { $server; } ); undef $server; }; no_leaks_ok { my $server; $server = Protocol::HTTP2::Server->new( on_request => sub { my ( $stream_id, $headers, $data ) = @_; my $s_stream = $server->response_stream( ':status' => 200, stream_id => $stream_id, # HTTP/1.1 Headers headers => [ 'server' => 'perl-Protocol-HTTP2/0.16', 'cache-control' => 'max-age=3600', 'date' => 'Fri, 18 Apr 2014 07:27:11 GMT', 'last-modified' => 'Thu, 27 Feb 2014 10:30:37 GMT', ], ); $s_stream->send('a'); $s_stream->send('b'); $s_stream->close; }, ); my $client = Protocol::HTTP2::Client->new; $client->request( # HTTP/2 headers ':scheme' => 'http', ':authority' => 'localhost:8000', ':path' => '/', ':method' => 'GET', # HTTP/1.1 headers headers => [ 'accept' => '*/*', 'user-agent' => 'perl-Protocol-HTTP2/0.16', ], # Callback when receive server's response on_done => sub { my ( $headers, $data ) = @_; }, ); fake_connect( $server, $client ); undef $client; undef $server; }; no_leaks_ok { my $cancel = 0; my $s_stream; my $server; $server = Protocol::HTTP2::Server->new( on_request => sub { my ( $stream_id, $headers, $data ) = @_; $s_stream = $server->response_stream( ':status' => 200, stream_id => $stream_id, # HTTP/1.1 Headers headers => [ 'server' => 'perl-Protocol-HTTP2/0.16', ], on_cancel => sub { $cancel = 1; } ); $s_stream->send('a'); }, ); my $client = Protocol::HTTP2::Client->new; $client->request( # HTTP/2 headers ':scheme' => 'http', ':authority' => 'localhost:8000', ':path' => '/', ':method' => 'GET', # HTTP/1.1 headers headers => [ 'user-agent' => 'perl-Protocol-HTTP2/0.16', ], on_headers => sub { 1; }, # Callback when receive server's response on_data => sub { my ( $chunk, $headers ) = @_; # cancel 0; }, ); fake_connect( $server, $client ); undef $client; undef $server; undef $s_stream; }; done_testing; 13_request_with_body.t100644000764000764 333513562447335 20024 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/tuse strict; use warnings; use Test::More; use Protocol::HTTP2::Client; use Protocol::HTTP2::Server; use lib 't/lib'; use PH2Test qw(fake_connect); subtest 'client POST' => sub { plan tests => 6; my $body = "DATA" x 10_000; my $location = "https://www.example.com/"; my $on_done = sub { my ( $headers, $data ) = @_; my %h = (@$headers); is $h{location}, $location, "correct redirect"; }; my %common = ( ':scheme' => 'http', ':authority' => 'localhost:8000', ':path' => '/', headers => [], on_done => $on_done, ); my $server; $server = Protocol::HTTP2::Server->new( on_request => sub { my ( $stream_id, $headers, $data ) = @_; my %h = (@$headers); if ( $h{':method'} eq 'POST' ) { is $body, $data, 'received correct POST body'; } elsif ( $h{':method'} eq 'PUT' ) { is $body, $data, 'received correct PUT body'; } elsif ( $h{':method'} eq 'OPTIONS' ) { is $data, undef, 'no body for OPTIONS'; } $server->response_stream( ':status' => 302, stream_id => $stream_id, headers => [ location => $location ], ); }, ); my $client = Protocol::HTTP2::Client->new; $client->request( %common, ':method' => 'POST', data => $body, )->request( %common, ':method' => 'OPTIONS', )->request( %common, ':method' => 'PUT', data => $body, ); fake_connect( $server, $client ); }; done_testing; 14_keepalive.t100644000764000764 506413562447335 16233 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/tuse strict; use warnings; use Test::More; use Protocol::HTTP2::Client; use Protocol::HTTP2::Server; use Protocol::HTTP2::Constants qw(:errors); use lib 't/lib'; use PH2Test qw(fake_connect); my %common = ( ':scheme' => 'https', ':authority' => 'localhost:8000', ':path' => '/', ':method' => 'GET', headers => [], ); subtest 'sequential requests' => sub { my $tests = 10; plan tests => $tests; my $server; $server = Protocol::HTTP2::Server->new( on_request => sub { $server->response_stream( ':status' => 204, stream_id => shift, headers => [], ); }, ); my $client = Protocol::HTTP2::Client->new; my $req; $req = sub { return if --$tests < 0; pass "request $tests"; $client->request( %common, on_done => $req ); }; $req->(); fake_connect( $server, $client ); }; subtest 'client keepalive' => sub { my $tests = 10; plan tests => $tests + 2; my $server; $server = Protocol::HTTP2::Server->new( on_request => sub { $server->response_stream( ':status' => 204, stream_id => shift, headers => [], ); }, ); my $client = Protocol::HTTP2::Client->new->keepalive(1); for my $i ( 1 .. $tests ) { $client->request( %common, on_done => sub { pass "request $i"; }, ); fake_connect( $server, $client ); } $client->close; fake_connect( $server, $client ); eval { $client->request(%common); }; ok $@, "request failed after close"; like $@, qr/closed/, "connection closed"; }; subtest 'client no keepalive' => sub { plan tests => 2; my $server; $server = Protocol::HTTP2::Server->new( on_request => sub { $server->response_stream( ':status' => 204, stream_id => shift, headers => [], ); }, ); my $client = Protocol::HTTP2::Client->new( keepalive => 0, on_error => sub { is( shift, PROTOCOL_ERROR, "request failed" ); }, ); $client->request( %common, on_done => sub { pass "request complete"; }, ); fake_connect( $server, $client ); $client->request( %common, on_done => sub { fail "keepalive?"; } ); fake_connect( $server, $client ); }; done_testing; continuation.request.data100644000764000764 7044013562447335 20651 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/tPRI * HTTP/2.0 SM d?A|=>gV*/*Tݗ5+b\Sns.1frva͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳfSns.1frvb͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳfSns.1frvd͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳfSns.1frvh?͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳfSns.1frvh͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛60 lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳfSns.1frvh͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳfSns.1frvh͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳfSns.1frvi?͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳf͛6lٳfmPH2ClientServerTest.pm100644000764000764 1240513562447335 20475 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/t/libpackage PH2ClientServerTest; use strict; use warnings; use AnyEvent; use AnyEvent::Socket; use AnyEvent::Handle; use Net::SSLeay; use AnyEvent::TLS; use Protocol::HTTP2; use Protocol::HTTP2::Client; use Protocol::HTTP2::Server; use Protocol::HTTP2::Constants qw(const_name); use Exporter qw(import); our @EXPORT = qw(client server check_tls); use Carp; sub check_tls { my (%opts) = @_; return exists $opts{npn} ? exists &Net::SSLeay::P_next_proto_negotiated : exists $opts{alpn} ? exists &Net::SSLeay::P_alpn_selected : 1; } sub server { my (%h) = @_; my $cb = delete $h{test_cb} or croak "no servers test_cb"; my $port = delete $h{port} or croak "no port availiable"; my $host = delete $h{host}; my $tls_crt = delete $h{"tls_crt"}; my $tls_key = delete $h{"tls_key"}; my $w = AnyEvent->condvar; tcp_server $host, $port, sub { my ( $fh, $host, $port ) = @_; my $handle; my $tls; if ( !$h{upgrade} && ( $h{npn} || $h{alpn} ) ) { eval { $tls = AnyEvent::TLS->new( method => 'tlsv1', cert_file => $tls_crt, key_file => $tls_key, ); if ( $h{npn} ) { # NPN (Net-SSLeay > 1.45, openssl >= 1.0.1) Net::SSLeay::CTX_set_next_protos_advertised_cb( $tls->ctx, [Protocol::HTTP2::ident_tls] ); } if ( $h{alpn} ) { # ALPN (Net-SSLeay > 1.55, openssl >= 1.0.2) Net::SSLeay::CTX_set_alpn_select_cb( $tls->ctx, [Protocol::HTTP2::ident_tls] ); } }; if ($@) { croak "Some problem with SSL CTX: $@" . Net::SSLeay::print_errs(); } } $handle = AnyEvent::Handle->new( fh => $fh, autocork => 1, defined $tls ? ( tls => "accept", tls_ctx => $tls ) : (), on_error => sub { $_[0]->destroy; print "connection error\n"; }, on_eof => sub { $handle->destroy; } ); my $server = Protocol::HTTP2::Server->new(%h); $cb->($server); # First send settings to peer while ( my $frame = $server->next_frame ) { $handle->push_write($frame); } $handle->on_read( sub { my $handle = shift; $server->feed( $handle->{rbuf} ); $handle->{rbuf} = undef; while ( my $frame = $server->next_frame ) { $handle->push_write($frame); } $handle->push_shutdown if $server->shutdown; } ); }; my $res = $w->recv; croak("error occured\n") unless $res; } sub client { my (%h) = @_; my $port = delete $h{port} or croak "no port availiable"; my $tls; my $host = delete $h{host}; if ( delete $h{upgrade} ) { $h{upgrade} = 1; } elsif ( $h{npn} || $h{alpn} ) { eval { $tls = AnyEvent::TLS->new( method => 'tlsv1', ); if ( delete $h{npn} ) { # NPN (Net-SSLeay > 1.45, openssl >= 1.0.1) Net::SSLeay::CTX_set_next_proto_select_cb( $tls->ctx, [Protocol::HTTP2::ident_tls] ); } if ( delete $h{alpn} ) { # ALPN (Net-SSLeay > 1.55, openssl >= 1.0.2) Net::SSLeay::CTX_set_alpn_protos( $tls->ctx, [Protocol::HTTP2::ident_tls] ); } }; if ($@) { croak "Some problem with SSL CTX: $@\n"; } } my $cb = delete $h{test_cb} or croak "no clients test_cb"; my $client = Protocol::HTTP2::Client->new(%h); $cb->($client); my $w = AnyEvent->condvar; tcp_connect $host, $port, sub { my ($fh) = @_ or do { print "connection failed: $!\n"; $w->send(0); return; }; my $handle; $handle = AnyEvent::Handle->new( fh => $fh, defined $tls ? ( tls => "connect", tls_ctx => $tls, ) : (), autocork => 1, on_error => sub { $_[0]->destroy; print "connection error\n"; $w->send(0); }, on_eof => sub { $handle->destroy; $w->send(1); } ); # First write preface to peer while ( my $frame = $client->next_frame ) { $handle->push_write($frame); } $handle->on_read( sub { my $handle = shift; $client->feed( $handle->{rbuf} ); $handle->{rbuf} = undef; while ( my $frame = $client->next_frame ) { $handle->push_write($frame); } $handle->push_shutdown if $client->shutdown; } ); }; my $res = $w->recv; croak("error occured\n") unless $res; } 1; PH2Test.pm100644000764000764 160313562447335 16125 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10/t/libpackage PH2Test; use strict; use warnings; use Protocol::HTTP2::Trace qw(bin2hex); use Exporter qw(import); our @EXPORT = qw(hstr binary_eq fake_connect); sub hstr { my $str = shift; $str =~ s/\#.*//g; $str =~ s/\s//g; my @a = ( $str =~ /../g ); return pack "C*", map { hex $_ } @a; } sub binary_eq { my ( $b1, $b2 ) = @_; if ( $b1 eq $b2 ) { return 1; } else { $b1 = bin2hex($b1); $b2 = bin2hex($b2); chomp $b1; chomp $b2; print "$b1\n not equal \n$b2 \n"; return 0; } } sub fake_connect { my ( $server, $client ) = @_; my ( $clt_frame, $srv_frame ); do { $clt_frame = $client->next_frame; $srv_frame = $server->next_frame; $server->feed($clt_frame) if $clt_frame; $client->feed($srv_frame) if $srv_frame; } while ( $clt_frame || $srv_frame ); } 1; META.yml100644000764000764 573613562447335 14611 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10--- abstract: 'HTTP/2 protocol implementation (RFC 7540)' author: - 'Vladimir Lettiev ' build_requires: AnyEvent: '0' Net::SSLeay: '> 1.45' Test::LeakTrace: '0' Test::More: '0.98' Test::TCP: '0' configure_requires: Module::Build::Tiny: '0.035' dynamic_config: 0 generated_by: 'Minilla/v3.1.7, CPAN::Meta::Converter version 2.150010' license: perl meta-spec: url: http://module-build.sourceforge.net/META-spec-v1.4.html version: '1.4' name: Protocol-HTTP2 no_index: directory: - t - xt - inc - share - eg - examples - author - builder provides: Protocol::HTTP2: file: lib/Protocol/HTTP2.pm version: '1.10' Protocol::HTTP2::Client: file: lib/Protocol/HTTP2/Client.pm Protocol::HTTP2::Connection: file: lib/Protocol/HTTP2/Connection.pm Protocol::HTTP2::Constants: file: lib/Protocol/HTTP2/Constants.pm Protocol::HTTP2::Frame: file: lib/Protocol/HTTP2/Frame.pm Protocol::HTTP2::Frame::Continuation: file: lib/Protocol/HTTP2/Frame/Continuation.pm Protocol::HTTP2::Frame::Data: file: lib/Protocol/HTTP2/Frame/Data.pm Protocol::HTTP2::Frame::Goaway: file: lib/Protocol/HTTP2/Frame/Goaway.pm Protocol::HTTP2::Frame::Headers: file: lib/Protocol/HTTP2/Frame/Headers.pm Protocol::HTTP2::Frame::Ping: file: lib/Protocol/HTTP2/Frame/Ping.pm Protocol::HTTP2::Frame::Priority: file: lib/Protocol/HTTP2/Frame/Priority.pm Protocol::HTTP2::Frame::Push_promise: file: lib/Protocol/HTTP2/Frame/Push_promise.pm Protocol::HTTP2::Frame::Rst_stream: file: lib/Protocol/HTTP2/Frame/Rst_stream.pm Protocol::HTTP2::Frame::Settings: file: lib/Protocol/HTTP2/Frame/Settings.pm Protocol::HTTP2::Frame::Window_update: file: lib/Protocol/HTTP2/Frame/Window_update.pm Protocol::HTTP2::HeaderCompression: file: lib/Protocol/HTTP2/HeaderCompression.pm Protocol::HTTP2::Huffman: file: lib/Protocol/HTTP2/Huffman.pm Protocol::HTTP2::HuffmanCodes: file: lib/Protocol/HTTP2/HuffmanCodes.pm Protocol::HTTP2::Server: file: lib/Protocol/HTTP2/Server.pm Protocol::HTTP2::Server::Stream: file: lib/Protocol/HTTP2/Server.pm Protocol::HTTP2::StaticTable: file: lib/Protocol/HTTP2/StaticTable.pm Protocol::HTTP2::Stream: file: lib/Protocol/HTTP2/Stream.pm Protocol::HTTP2::Trace: file: lib/Protocol/HTTP2/Trace.pm Protocol::HTTP2::Upgrade: file: lib/Protocol/HTTP2/Upgrade.pm requires: MIME::Base64: '3.11' Scalar::Util: '0' perl: '5.008005' resources: bugtracker: https://github.com/vlet/p5-Protocol-HTTP2/issues homepage: https://github.com/vlet/p5-Protocol-HTTP2 repository: git://github.com/vlet/p5-Protocol-HTTP2.git version: '1.10' x_contributors: - 'Daniil Bondarev ' - 'Felipe Gasper ' - 'Junho Choi ' - 'Mohammad S Anwar ' - 'gregor herrmann ' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' x_static_install: 1 MANIFEST100644000764000764 271113562447335 14457 0ustar00cruxcrux000000000000Protocol-HTTP2-1.10Build.PL Changes LICENSE META.json README.md cpanfile examples/client-anyevent.pl examples/client-io-socket-ssl.pl examples/client-tls-anyevent.pl examples/extract_huff_codes.pl examples/extract_static_table.pl examples/server-anyevent.pl examples/server-io-socket-ssl.pl examples/server-tls-anyevent.pl examples/test.crt examples/test.key lib/Protocol/HTTP2.pm lib/Protocol/HTTP2/Client.pm lib/Protocol/HTTP2/Connection.pm lib/Protocol/HTTP2/Constants.pm lib/Protocol/HTTP2/Frame.pm lib/Protocol/HTTP2/Frame/Continuation.pm lib/Protocol/HTTP2/Frame/Data.pm lib/Protocol/HTTP2/Frame/Goaway.pm lib/Protocol/HTTP2/Frame/Headers.pm lib/Protocol/HTTP2/Frame/Ping.pm lib/Protocol/HTTP2/Frame/Priority.pm lib/Protocol/HTTP2/Frame/Push_promise.pm lib/Protocol/HTTP2/Frame/Rst_stream.pm lib/Protocol/HTTP2/Frame/Settings.pm lib/Protocol/HTTP2/Frame/Window_update.pm lib/Protocol/HTTP2/HeaderCompression.pm lib/Protocol/HTTP2/Huffman.pm lib/Protocol/HTTP2/HuffmanCodes.pm lib/Protocol/HTTP2/Server.pm lib/Protocol/HTTP2/StaticTable.pm lib/Protocol/HTTP2/Stream.pm lib/Protocol/HTTP2/Trace.pm lib/Protocol/HTTP2/Upgrade.pm minil.toml t/00_compile.t t/01_HeaderCompression.t t/02_Huffman.t t/03_connection.t t/04_continuation.t t/05_trace.t t/06_upgrade.t t/07_ping.t t/08_priority.t t/09_client_server_tcp.t t/10_settings.t t/11_server_stream.t t/12_leaks.t t/13_request_with_body.t t/14_keepalive.t t/continuation.request.data t/lib/PH2ClientServerTest.pm t/lib/PH2Test.pm META.yml MANIFEST