Build.PL100644001750001750 45514622427401 14546 0ustar00vletvlet000000000000Protocol-HTTP2-1.11# ========================================================================= # 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(); Changes100644001750001750 1473014622427401 14606 0ustar00vletvlet000000000000Protocol-HTTP2-1.11Revision history for Perl extension Protocol-HTTP2 1.11 2024-05-19T16:55:28Z - Fix minor typos and spelling errors (Yoshikazu Sawa) - Do not hardcode tlsv1 which is deprecated and disabled in some systems (Olivier Gayot) - Fix length-undef warnings in pre-5.12 perls (Felipe Gasper) 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 debugging 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 concurrent 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 - Split 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 compression 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 reference 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 LICENSE100644001750001750 4374614622427401 14331 0ustar00vletvlet000000000000Protocol-HTTP2-1.11This 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.json100644001750001750 1146014622427401 14731 0ustar00vletvlet000000000000Protocol-HTTP2-1.11{ "abstract" : "HTTP/2 protocol implementation (RFC 7540)", "author" : [ "Vladimir Lettiev " ], "dynamic_config" : 0, "generated_by" : "Minilla/v3.1.21", "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.11" }, "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" : { "type" : "git", "url" : "https://github.com/vlet/p5-Protocol-HTTP2.git", "web" : "https://github.com/vlet/p5-Protocol-HTTP2" } }, "version" : "1.11", "x_contributors" : [ "Daniil Bondarev ", "Felipe Gasper ", "Junho Choi ", "Mohammad S Anwar ", "Olivier Gayot ", "gregor herrmann ", "yoshikazusawa <883514+yoshikazusawa@users.noreply.github.com>" ], "x_serialization_backend" : "JSON::PP version 4.07", "x_static_install" : 1 } README.md100644001750001750 775514622427401 14563 0ustar00vletvlet000000000000Protocol-HTTP2-1.11# 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 implemented - ± -- 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 cpanfile100644001750001750 64614622427401 14760 0ustar00vletvlet000000000000Protocol-HTTP2-1.11requires '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.pl100644001750001750 410314622427401 20544 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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 occurred: %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.pl100644001750001750 240714622427401 21414 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.pl100644001750001750 646014622427401 21354 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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 occurred: %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.pl100644001750001750 175614622427401 21311 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.pl100644001750001750 206614622427401 21635 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.pl100644001750001750 437214622427401 20604 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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 occurred: %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.pl100644001750001750 311614622427401 21442 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.pl100644001750001750 772614622427401 21412 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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 occurred: %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.crt100644001750001750 251214622427401 16575 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.key100644001750001750 321314622427401 16574 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.pm100644001750001750 722614622427401 17043 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/lib/Protocolpackage Protocol::HTTP2; use 5.008005; use strict; use warnings; our $VERSION = "1.11"; 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 implemented =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.pm100644001750001750 3031014622427401 20267 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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 = Protocol::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.pm100644001750001750 3377414622427401 21171 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.pm100644001750001750 710314622427401 21011 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.pm100644001750001750 1221414622427401 20106 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.pm100644001750001750 155114622427401 22542 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.pm100644001750001750 443214622427401 20742 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.pm100644001750001750 227014622427401 21316 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.pm100644001750001750 462514622427401 21450 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.pm100644001750001750 160514622427401 20765 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.pm100644001750001750 225714622427401 21715 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.pm100644001750001750 327414622427401 22551 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.pm100644001750001750 206614622427401 22215 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.pm100644001750001750 566514622427401 21702 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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 (length $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.pm100644001750001750 272314622427401 22703 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.pm100644001750001750 2657114622427401 22501 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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 read 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 read 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.pm100644001750001750 225314622427401 20422 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.pm100644001750001750 2121614622427401 21420 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.pm100644001750001750 3305214622427401 20325 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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 = Protocol::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' => 'localhost: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.pm100644001750001750 605614622427401 21242 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.pm100644001750001750 3010614622427401 20307 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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 ( defined( $s->{blocked_data} ) && 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 children 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.pm100644001750001750 317014622427401 20073 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.pm100644001750001750 704614622427401 20432 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.toml100644001750001750 11514622427401 15250 0ustar00vletvlet000000000000Protocol-HTTP2-1.11name = "Protocol-HTTP2" module_maker="ModuleBuildTiny" # badges = ["travis"] 00_compile.t100644001750001750 12414622427401 15622 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/tuse strict; use Test::More; use_ok $_ for qw( Protocol::HTTP2 ); done_testing; 01_HeaderCompression.t100644001750001750 1774214622427401 17663 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.t100644001750001750 52414622427401 15564 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.t100644001750001750 1015214622427401 16376 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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 occurred"); } ); 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.t100644001750001750 201614622427401 16732 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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 occurred"); } ); 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.t100644001750001750 37614622427401 15306 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.t100644001750001750 254114622427401 15654 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.t100644001750001750 351414622427401 15164 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.t100644001750001750 507314622427401 16113 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.t100644001750001750 1016614622427401 17764 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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 "skipped $test: feature not available\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 occurred: " . 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 occurred: " . 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.t100644001750001750 160014622427401 16053 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.t100644001750001750 605514622427401 17106 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.t100644001750001750 623514622427401 15325 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.t100644001750001750 333514622427401 17765 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.t100644001750001750 506414622427401 16174 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.data100644001750001750 7044014622427401 20612 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.pm100644001750001750 1235314622427401 20440 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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 available"; 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( 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 STDERR "connection error: $_[2]: $!\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 occurred\n") unless $res; } sub client { my (%h) = @_; my $port = delete $h{port} or croak "no port available"; my $tls; my $host = delete $h{host}; if ( delete $h{upgrade} ) { $h{upgrade} = 1; } elsif ( $h{npn} || $h{alpn} ) { eval { $tls = AnyEvent::TLS->new(); 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 STDERR "connection error: $_[2]: $!\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 occurred\n") unless $res; } 1; PH2Test.pm100644001750001750 160314622427401 16066 0ustar00vletvlet000000000000Protocol-HTTP2-1.11/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.yml100644001750001750 612714622427401 14545 0ustar00vletvlet000000000000Protocol-HTTP2-1.11--- 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.21, 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.11' 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: https://github.com/vlet/p5-Protocol-HTTP2.git version: '1.11' x_contributors: - 'Daniil Bondarev ' - 'Felipe Gasper ' - 'Junho Choi ' - 'Mohammad S Anwar ' - 'Olivier Gayot ' - 'gregor herrmann ' - 'yoshikazusawa <883514+yoshikazusawa@users.noreply.github.com>' x_serialization_backend: 'CPAN::Meta::YAML version 0.018' x_static_install: 1 MANIFEST100644001750001750 271114622427401 14420 0ustar00vletvlet000000000000Protocol-HTTP2-1.11Build.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