├── .github ├── dependabot.yaml └── workflows │ ├── health.yaml │ ├── no-response.yml │ ├── post_summaries.yaml │ ├── publish.yaml │ └── test-package.yml ├── .gitignore ├── .test_config ├── AUTHORS ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── dart_test.yaml ├── example └── display_headers.dart ├── lib ├── http2.dart ├── multiprotocol_server.dart ├── src │ ├── artificial_server_socket.dart │ ├── async_utils │ │ └── async_utils.dart │ ├── byte_utils.dart │ ├── connection.dart │ ├── connection_preface.dart │ ├── error_handler.dart │ ├── flowcontrol │ │ ├── connection_queues.dart │ │ ├── queue_messages.dart │ │ ├── stream_queues.dart │ │ ├── window.dart │ │ └── window_handler.dart │ ├── frames │ │ ├── frame_defragmenter.dart │ │ ├── frame_reader.dart │ │ ├── frame_types.dart │ │ ├── frame_utils.dart │ │ ├── frame_writer.dart │ │ └── frames.dart │ ├── hpack │ │ ├── hpack.dart │ │ ├── huffman.dart │ │ └── huffman_table.dart │ ├── ping │ │ └── ping_handler.dart │ ├── settings │ │ └── settings.dart │ ├── streams │ │ └── stream_handler.dart │ └── sync_errors.dart └── transport.dart ├── manual_test └── out_of_stream_ids_test.dart ├── pubspec.yaml └── test ├── certificates ├── server_chain.pem └── server_key.pem ├── client_test.dart ├── multiprotocol_server_test.dart ├── server_test.dart ├── src ├── async_utils │ └── async_utils_test.dart ├── connection_preface_test.dart ├── error_matchers.dart ├── flowcontrol │ ├── connection_queues_test.dart │ ├── mocks.dart │ ├── mocks.mocks.dart │ ├── stream_queues_test.dart │ └── window_handler_test.dart ├── frames │ ├── frame_defragmenter_test.dart │ ├── frame_reader_test.dart │ ├── frame_writer_reader_test.dart │ └── frame_writer_test.dart ├── hpack │ ├── hpack_test.dart │ └── huffman_table_test.dart ├── ping │ └── ping_handler_test.dart ├── settings │ └── settings_handler_test.dart └── streams │ ├── helper.dart │ ├── simple_flow_test.dart │ ├── simple_push_test.dart │ └── streams_test.dart └── transport_test.dart /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | # Dependabot configuration file. 2 | version: 2 3 | 4 | updates: 5 | - package-ecosystem: github-actions 6 | directory: / 7 | schedule: 8 | interval: monthly 9 | labels: 10 | - autosubmit 11 | groups: 12 | github-actions: 13 | patterns: 14 | - "*" 15 | -------------------------------------------------------------------------------- /.github/workflows/health.yaml: -------------------------------------------------------------------------------- 1 | name: Health 2 | 3 | on: 4 | pull_request: 5 | branches: [ master ] 6 | types: [opened, synchronize, reopened, labeled, unlabeled] 7 | 8 | jobs: 9 | health: 10 | uses: dart-lang/ecosystem/.github/workflows/health.yaml@main 11 | with: 12 | coverage_web: false 13 | sdk: dev 14 | permissions: 15 | pull-requests: write 16 | -------------------------------------------------------------------------------- /.github/workflows/no-response.yml: -------------------------------------------------------------------------------- 1 | # A workflow to close issues where the author hasn't responded to a request for 2 | # more information; see https://github.com/actions/stale. 3 | 4 | name: No Response 5 | 6 | # Run as a daily cron. 7 | on: 8 | schedule: 9 | # Every day at 8am 10 | - cron: '0 8 * * *' 11 | 12 | # All permissions not specified are set to 'none'. 13 | permissions: 14 | issues: write 15 | pull-requests: write 16 | 17 | jobs: 18 | no-response: 19 | runs-on: ubuntu-latest 20 | if: ${{ github.repository_owner == 'dart-lang' }} 21 | steps: 22 | - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e 23 | with: 24 | # Don't automatically mark inactive issues+PRs as stale. 25 | days-before-stale: -1 26 | # Close needs-info issues and PRs after 14 days of inactivity. 27 | days-before-close: 14 28 | stale-issue-label: "needs-info" 29 | close-issue-message: > 30 | Without additional information we're not able to resolve this issue. 31 | Feel free to add more info or respond to any questions above and we 32 | can reopen the case. Thanks for your contribution! 33 | stale-pr-label: "needs-info" 34 | close-pr-message: > 35 | Without additional information we're not able to resolve this PR. 36 | Feel free to add more info or respond to any questions above. 37 | Thanks for your contribution! 38 | -------------------------------------------------------------------------------- /.github/workflows/post_summaries.yaml: -------------------------------------------------------------------------------- 1 | name: Comment on the pull request 2 | 3 | on: 4 | # Trigger this workflow after the Health workflow completes. This workflow will have permissions to 5 | # do things like create comments on the PR, even if the original workflow couldn't. 6 | workflow_run: 7 | workflows: 8 | - Health 9 | - Publish 10 | types: 11 | - completed 12 | 13 | jobs: 14 | upload: 15 | uses: dart-lang/ecosystem/.github/workflows/post_summaries.yaml@main 16 | permissions: 17 | pull-requests: write 18 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | # A CI configuration to auto-publish pub packages. 2 | 3 | name: Publish 4 | 5 | on: 6 | pull_request: 7 | branches: [ master ] 8 | push: 9 | tags: [ 'v[0-9]+.[0-9]+.[0-9]+' ] 10 | 11 | jobs: 12 | publish: 13 | if: ${{ github.repository_owner == 'dart-lang' }} 14 | uses: dart-lang/ecosystem/.github/workflows/publish.yaml@main 15 | permissions: 16 | id-token: write # Required for authentication using OIDC 17 | pull-requests: write # Required for writing the pull request note 18 | -------------------------------------------------------------------------------- /.github/workflows/test-package.yml: -------------------------------------------------------------------------------- 1 | name: Dart CI 2 | 3 | on: 4 | # Run on PRs and pushes to the default branch. 5 | push: 6 | branches: [ master ] 7 | pull_request: 8 | branches: [ master ] 9 | schedule: 10 | - cron: "0 0 * * 0" 11 | 12 | env: 13 | PUB_ENVIRONMENT: bot.github 14 | 15 | jobs: 16 | # Check code formatting and static analysis on a single OS (linux) 17 | # against Dart dev. 18 | analyze: 19 | runs-on: ubuntu-latest 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | sdk: [dev] 24 | steps: 25 | - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 26 | - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 27 | with: 28 | sdk: ${{ matrix.sdk }} 29 | - id: install 30 | name: Install dependencies 31 | run: dart pub get 32 | - name: Check formatting 33 | run: dart format --output=none --set-exit-if-changed . 34 | if: always() && steps.install.outcome == 'success' 35 | - name: Analyze code 36 | run: dart analyze --fatal-infos 37 | if: always() && steps.install.outcome == 'success' 38 | 39 | # Run tests on a matrix consisting of two dimensions: 40 | # 1. OS: ubuntu-latest, (macos-latest, windows-latest) 41 | # 2. release channel: dev 42 | test: 43 | needs: analyze 44 | runs-on: ${{ matrix.os }} 45 | strategy: 46 | fail-fast: false 47 | matrix: 48 | # Add macos-latest and/or windows-latest if relevant for this package. 49 | os: [ubuntu-latest] 50 | sdk: [3.2, dev] 51 | steps: 52 | - uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 53 | - uses: dart-lang/setup-dart@0a8a0fc875eb934c15d08629302413c671d3f672 54 | with: 55 | sdk: ${{ matrix.sdk }} 56 | - id: install 57 | name: Install dependencies 58 | run: dart pub get 59 | - name: Run VM tests 60 | run: dart test --platform vm 61 | if: always() && steps.install.outcome == 'success' 62 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Don’t commit the following directories created by pub. 2 | .dart_tool 3 | .packages 4 | pubspec.lock 5 | -------------------------------------------------------------------------------- /.test_config: -------------------------------------------------------------------------------- 1 | { 2 | "test_package": { 3 | "platforms" : ["vm"] 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Below is a list of people and organizations that have contributed 2 | # to the project. Names should be added to the list like so: 3 | # 4 | # Name/Organization 5 | 6 | Google Inc. <*@google.com> 7 | 8 | Alexandre Ardhuin 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2.3.1-wip 2 | 3 | - Require Dart 3.2 4 | - Add topics to `pubspec.yaml` 5 | 6 | ## 2.3.0 7 | 8 | - Only send updates on frames and pings being received when there are listeners, as to not fill up memory. 9 | 10 | ## 2.2.0 11 | 12 | - Transform headers to lowercase. 13 | - Expose pings to connection to enable the KEEPALIVE feature for gRPC. 14 | 15 | ## 2.1.0 16 | 17 | - Require Dart `3.0.0` 18 | - Require Dart `2.17.0`. 19 | - Send `WINDOW_UPDATE` frames for the connection to account for data being sent on closed streams until the `RST_STREAM` has been processed. 20 | 21 | ## 2.0.1 22 | 23 | - Simplify the implementation of `MultiProtocolHttpServer.close`. 24 | - Require Dart `2.15.0`. 25 | 26 | ## 2.0.0 27 | 28 | * Migrate to null safety. 29 | 30 | ## 1.0.1 31 | 32 | * Add `TransportConnection.onInitialPeerSettingsReceived` which fires when 33 | initial SETTINGS frame is received from the peer. 34 | 35 | ## 1.0.0 36 | 37 | * Graduate package to 1.0. 38 | * `package:http2/http2.dart` now reexports `package:http2/transport.dart`. 39 | 40 | ## 0.1.9 41 | 42 | * Discard messages incoming after stream cancellation. 43 | 44 | ## 0.1.8+2 45 | 46 | * On connection termination, try to dispatch existing messages, thereby avoiding 47 | terminating existing streams. 48 | 49 | * Fix `ClientTransportConnection.isOpen` to return `false` if we have exhausted 50 | the number of max-concurrent-streams. 51 | 52 | ## 0.1.8+1 53 | 54 | * Switch all uppercase constants from `dart:convert` to lowercase. 55 | 56 | ## 0.1.8 57 | 58 | * More changes required for making tests pass under Dart 2.0 runtime. 59 | * Modify sdk constraint to require '>=2.0.0-dev.40.0'. 60 | 61 | ## 0.1.7 62 | 63 | * Fixes for Dart 2.0. 64 | 65 | ## 0.1.6 66 | 67 | * Strong mode fixes and other cleanup. 68 | 69 | ## 0.1.5 70 | 71 | * Removed use of new `Function` syntax, since it isn't fully supported in Dart 72 | 1.24. 73 | 74 | ## 0.1.4 75 | 76 | * Added an `onActiveStateChanged` callback to `Connection`, which is invoked when 77 | the connection changes state from idle to active or from active to idle. This 78 | can be used to implement an idle connection timeout. 79 | 80 | ## 0.1.3 81 | 82 | * Fixed a bug where a closed window would not open correctly due to an increase 83 | in initial window size. 84 | 85 | ## 0.1.2 86 | 87 | * The endStream bit is now set on the requested frame, instead of on an empty 88 | data frame following it. 89 | * Added an `onTerminated` hook that is called when a TransportStream receives 90 | a RST_STREAM frame. 91 | 92 | ## 0.1.1+2 93 | 94 | * Add errorCode to exception toString message. 95 | 96 | ## 0.1.1+1 97 | 98 | * Fixing a performance issue in case the underlying socket is not writeable 99 | * Allow clients of MultiProtocolHttpServer to supply [http.ServerSettings] 100 | * Allow the draft version 'h2-14' in the ALPN protocol negogiation. 101 | 102 | ## 0.1.1 103 | 104 | * Adding support for MultiProtocolHttpServer in the 105 | `package:http2/multiprotocol_server.dart` library 106 | 107 | ## 0.1.0 108 | 109 | * First version of a HTTP/2 transport implementation in the 110 | `package:http2/transport.dart` library 111 | 112 | ## 0.0.1 113 | 114 | - Initial version 115 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Want to contribute? Great! First, read this page (including the small print at 2 | the end). 3 | 4 | ### Before you contribute 5 | Before we can use your code, you must sign the 6 | [Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual) 7 | (CLA), which you can do online. The CLA is necessary mainly because you own the 8 | copyright to your changes, even after your contribution becomes part of our 9 | codebase, so we need your permission to use and distribute your code. We also 10 | need to be sure of various other things—for instance that you'll tell us if you 11 | know that your code infringes on other people's patents. You don't have to sign 12 | the CLA until after you've submitted your code for review and a member has 13 | approved it, but you must do it before we can put your code into our codebase. 14 | 15 | Before you start working on a larger contribution, you should get in touch with 16 | us first through the issue tracker with your idea so that we can help out and 17 | possibly guide you. Coordinating up front makes it much easier to avoid 18 | frustration later on. 19 | 20 | ### Code reviews 21 | All submissions, including submissions by project members, require review. 22 | 23 | ### File headers 24 | All files in the project must start with the following header. 25 | 26 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 27 | // for details. All rights reserved. Use of this source code is governed by a 28 | // BSD-style license that can be found in the LICENSE file. 29 | 30 | ### The small print 31 | Contributions made by corporations are covered by a different agreement than the 32 | one above, the 33 | [Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate). 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2015, the Dart project authors. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided 12 | with the distribution. 13 | * Neither the name of Google LLC nor the names of its 14 | contributors may be used to endorse or promote products derived 15 | from this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!IMPORTANT] 2 | > This repo has moved to https://github.com/dart-lang/http/tree/master/pkgs/http2 3 | 4 | [![Dart CI](https://github.com/dart-lang/http2/actions/workflows/test-package.yml/badge.svg)](https://github.com/dart-lang/http2/actions/workflows/test-package.yml) 5 | [![pub package](https://img.shields.io/pub/v/http2.svg)](https://pub.dev/packages/http2) 6 | [![package publisher](https://img.shields.io/pub/publisher/http2.svg)](https://pub.dev/packages/http2/publisher) 7 | 8 | This library provides an http/2 interface on top of a bidirectional stream of bytes. 9 | 10 | ## Usage 11 | 12 | Here is a minimal example of connecting to a http/2 capable server, requesting 13 | a resource and iterating over the response. 14 | 15 | ```dart 16 | import 'dart:convert'; 17 | import 'dart:io'; 18 | 19 | import 'package:http2/http2.dart'; 20 | 21 | Future main() async { 22 | final uri = Uri.parse('https://www.google.com/'); 23 | 24 | final transport = ClientTransportConnection.viaSocket( 25 | await SecureSocket.connect( 26 | uri.host, 27 | uri.port, 28 | supportedProtocols: ['h2'], 29 | ), 30 | ); 31 | 32 | final stream = transport.makeRequest( 33 | [ 34 | Header.ascii(':method', 'GET'), 35 | Header.ascii(':path', uri.path), 36 | Header.ascii(':scheme', uri.scheme), 37 | Header.ascii(':authority', uri.host), 38 | ], 39 | endStream: true, 40 | ); 41 | 42 | await for (var message in stream.incomingMessages) { 43 | if (message is HeadersStreamMessage) { 44 | for (var header in message.headers) { 45 | final name = utf8.decode(header.name); 46 | final value = utf8.decode(header.value); 47 | print('Header: $name: $value'); 48 | } 49 | } else if (message is DataStreamMessage) { 50 | // Use [message.bytes] (but respect 'content-encoding' header) 51 | } 52 | } 53 | await transport.finish(); 54 | } 55 | ``` 56 | 57 | An example with better error handling is available [here][example]. 58 | 59 | See the [API docs][api] for more details. 60 | 61 | ## Features and bugs 62 | 63 | Please file feature requests and bugs at the [issue tracker][tracker]. 64 | 65 | [tracker]: https://github.com/dart-lang/http2/issues 66 | [api]: https://pub.dev/documentation/http2/latest/ 67 | [example]: https://github.com/dart-lang/http2/blob/master/example/display_headers.dart 68 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # https://dart.dev/tools/analysis#the-analysis-options-file 2 | include: package:dart_flutter_team_lints/analysis_options.yaml 3 | 4 | analyzer: 5 | language: 6 | strict-casts: true 7 | errors: 8 | # Disabled as there are several dozen violations. 9 | constant_identifier_names: ignore 10 | -------------------------------------------------------------------------------- /dart_test.yaml: -------------------------------------------------------------------------------- 1 | tags: 2 | flaky: # Tests that should be run as a separate job on Travis 3 | -------------------------------------------------------------------------------- /example/display_headers.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:convert'; 7 | import 'dart:io'; 8 | 9 | import 'package:http2/transport.dart'; 10 | 11 | void main(List args) async { 12 | if (args.length != 1) { 13 | print('Usage: dart display_headers.dart '); 14 | exit(1); 15 | } 16 | 17 | var uriArg = args[0]; 18 | 19 | if (!uriArg.startsWith('https://')) { 20 | print('URI must start with https://'); 21 | exit(1); 22 | } 23 | 24 | var uri = Uri.parse(uriArg); 25 | 26 | var socket = await connect(uri); 27 | 28 | // The default client settings will disable server pushes. We 29 | // therefore do not need to deal with [stream.peerPushes]. 30 | var transport = ClientTransportConnection.viaSocket(socket); 31 | 32 | var headers = [ 33 | Header.ascii(':method', 'GET'), 34 | Header.ascii(':path', uri.path), 35 | Header.ascii(':scheme', uri.scheme), 36 | Header.ascii(':authority', uri.host), 37 | ]; 38 | 39 | var stream = transport.makeRequest(headers, endStream: true); 40 | await for (var message in stream.incomingMessages) { 41 | if (message is HeadersStreamMessage) { 42 | for (var header in message.headers) { 43 | var name = utf8.decode(header.name); 44 | var value = utf8.decode(header.value); 45 | print('$name: $value'); 46 | } 47 | } else if (message is DataStreamMessage) { 48 | // Use [message.bytes] (but respect 'content-encoding' header) 49 | } 50 | } 51 | await transport.finish(); 52 | } 53 | 54 | Future connect(Uri uri) async { 55 | var useSSL = uri.scheme == 'https'; 56 | if (useSSL) { 57 | var secureSocket = await SecureSocket.connect(uri.host, uri.port, 58 | supportedProtocols: ['h2']); 59 | if (secureSocket.selectedProtocol != 'h2') { 60 | throw Exception('Failed to negogiate http/2 via alpn. Maybe server ' 61 | "doesn't support http/2."); 62 | } 63 | return secureSocket; 64 | } else { 65 | return await Socket.connect(uri.host, uri.port); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/http2.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | /// This library provides an http/2 interface on top of a bidirectional stream 6 | /// of bytes. 7 | /// 8 | /// The client and server sides can be created via [ClientTransportStream] and 9 | /// [ServerTransportStream] respectively. Both sides can be configured via 10 | /// settings (see [ClientSettings] and [ServerSettings]). The settings will be 11 | /// communicated to the remote peer (if necessary) and will be valid during the 12 | /// entire lifetime of the connection. 13 | /// 14 | /// A http/2 transport allows a client to open a bidirectional stream (see 15 | /// [ClientTransportConnection.makeRequest]) and a server can open (or push) a 16 | /// unidirectional stream to the client via [ServerTransportStream.push]. 17 | /// 18 | /// In both cases (unidirectional and bidirectional stream), one can send 19 | /// headers and data to the other side (via [HeadersStreamMessage] and 20 | /// [DataStreamMessage]). These messages are ordered and will arrive in the same 21 | /// order as they were sent (data messages may be split up into multiple smaller 22 | /// chunks or might be combined). 23 | /// 24 | /// In the most common case, each direction will send one [HeadersStreamMessage] 25 | /// followed by zero or more [DataStreamMessage]s. 26 | /// 27 | /// Establishing a bidirectional stream of bytes to a server is up to the user 28 | /// of this library. There are 3 common ways to achive this 29 | /// 30 | /// * connect to a server via SSL and use the ALPN (SSL) protocol extension 31 | /// to negotiate with the server to speak http/2 (the ALPN protocol 32 | /// identifier for http/2 is `h2`) 33 | /// 34 | /// * have prior knowledge about the server - i.e. know ahead of time that 35 | /// the server will speak http/2 via an unencrypted tcp connection 36 | /// 37 | /// * use a http/1.1 connection and upgrade it to http/2 38 | /// 39 | /// The first way is the most common way and can be done in Dart by using 40 | /// `dart:io`s secure socket implementation (by using a `SecurityContext` and 41 | /// including 'h2' in the list of protocols used for ALPN). 42 | /// 43 | /// A simple example on how to connect to a http/2 capable server and 44 | /// requesting a resource is available at https://github.com/dart-lang/http2/blob/master/example/display_headers.dart. 45 | library http2.http2; 46 | 47 | import 'transport.dart'; 48 | export 'transport.dart'; 49 | -------------------------------------------------------------------------------- /lib/multiprotocol_server.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:io'; 7 | 8 | import 'src/artificial_server_socket.dart'; 9 | import 'transport.dart' as http2; 10 | 11 | /// Handles protocol negotiation with HTTP/1.1 and HTTP/2 clients. 12 | /// 13 | /// Given a (host, port) pair and a [SecurityContext], [MultiProtocolHttpServer] 14 | /// will negotiate with the client whether HTTP/1.1 or HTTP/2 should be spoken. 15 | /// 16 | /// The user must supply 2 callback functions to [startServing], which: 17 | /// * one handles HTTP/1.1 clients (called with a [HttpRequest]) 18 | /// * one handles HTTP/2 clients (called with a [http2.ServerTransportStream]) 19 | class MultiProtocolHttpServer { 20 | final SecureServerSocket _serverSocket; 21 | final http2.ServerSettings? _settings; 22 | 23 | late _ServerSocketController _http11Controller; 24 | late HttpServer _http11Server; 25 | 26 | final StreamController _http2Controller = 27 | StreamController(); 28 | Stream get _http2Server => 29 | _http2Controller.stream; 30 | 31 | final _http2Connections = {}; 32 | 33 | MultiProtocolHttpServer._(this._serverSocket, this._settings) { 34 | _http11Controller = 35 | _ServerSocketController(_serverSocket.address, _serverSocket.port); 36 | _http11Server = HttpServer.listenOn(_http11Controller.stream); 37 | } 38 | 39 | /// Binds a new [SecureServerSocket] with a security [context] at [port] and 40 | /// [address] (see [SecureServerSocket.bind] for a description of supported 41 | /// types for [address]). 42 | /// 43 | /// Optionally [settings] can be supplied which will be used for HTTP/2 44 | /// clients. 45 | /// 46 | /// See also [startServing]. 47 | static Future bind( 48 | Object? address, int port, SecurityContext context, 49 | {http2.ServerSettings? settings}) async { 50 | context.setAlpnProtocols(['h2', 'h2-14', 'http/1.1'], true); 51 | var secureServer = await SecureServerSocket.bind(address, port, context); 52 | return MultiProtocolHttpServer._(secureServer, settings); 53 | } 54 | 55 | /// The port this multi-protocol HTTP server runs on. 56 | int get port => _serverSocket.port; 57 | 58 | /// The address this multi-protocol HTTP server runs on. 59 | InternetAddress get address => _serverSocket.address; 60 | 61 | /// Starts listening for HTTP/1.1 and HTTP/2 clients and calls the given 62 | /// callbacks for new clients. 63 | /// 64 | /// It is expected that [callbackHttp11] and [callbackHttp2] will never throw 65 | /// an exception (i.e. these must take care of error handling themselves). 66 | void startServing(void Function(HttpRequest) callbackHttp11, 67 | void Function(http2.ServerTransportStream) callbackHttp2, 68 | {void Function(dynamic error, StackTrace)? onError}) { 69 | // 1. Start listening on the real [SecureServerSocket]. 70 | _serverSocket.listen((SecureSocket socket) { 71 | var protocol = socket.selectedProtocol; 72 | if (protocol == null || protocol == 'http/1.1') { 73 | _http11Controller.addHttp11Socket(socket); 74 | } else if (protocol == 'h2' || protocol == 'h2-14') { 75 | var connection = http2.ServerTransportConnection.viaSocket(socket, 76 | settings: _settings); 77 | _http2Connections.add(connection); 78 | connection.incomingStreams.listen(_http2Controller.add, 79 | onError: onError, 80 | onDone: () => _http2Connections.remove(connection)); 81 | } else { 82 | socket.destroy(); 83 | throw Exception('Unexpected negotiated ALPN protocol: $protocol.'); 84 | } 85 | }, onError: onError); 86 | 87 | // 2. Drain all incoming http/1.1 and http/2 connections and call the 88 | // respective handlers. 89 | _http11Server.listen(callbackHttp11); 90 | _http2Server.listen(callbackHttp2); 91 | } 92 | 93 | /// Closes this [MultiProtocolHttpServer]. 94 | /// 95 | /// Completes once everything has been successfully shut down. 96 | Future close({bool force = false}) => 97 | _serverSocket.close().whenComplete(() => Future.wait([ 98 | _http11Server.close(force: force), 99 | for (var c in _http2Connections) force ? c.terminate() : c.finish() 100 | ])); 101 | } 102 | 103 | /// An internal helper class. 104 | class _ServerSocketController { 105 | final InternetAddress address; 106 | final int port; 107 | final StreamController _controller = StreamController(); 108 | 109 | _ServerSocketController(this.address, this.port); 110 | 111 | ArtificialServerSocket get stream { 112 | return ArtificialServerSocket(address, port, _controller.stream); 113 | } 114 | 115 | void addHttp11Socket(Socket socket) { 116 | _controller.add(socket); 117 | } 118 | 119 | Future close() => _controller.close(); 120 | } 121 | -------------------------------------------------------------------------------- /lib/src/artificial_server_socket.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:io'; 7 | 8 | /// Custom implementation of the [ServerSocket] interface. 9 | /// 10 | /// This class can be used to create a [ServerSocket] using [Stream] and 11 | /// a [InternetAddress] and `port` (an example use case is to filter [Socket]s 12 | /// and keep the [ServerSocket] interface for APIs that expect it, 13 | /// e.g. `new HttpServer.listenOn()`). 14 | class ArtificialServerSocket extends StreamView 15 | implements ServerSocket { 16 | ArtificialServerSocket(this.address, this.port, Stream stream) 17 | : super(stream); 18 | 19 | // ######################################################################## 20 | // These are the methods of [ServerSocket] in addition to [Stream]. 21 | // ######################################################################## 22 | 23 | @override 24 | final InternetAddress address; 25 | 26 | @override 27 | final int port; 28 | 29 | /// Closing of an [ArtificialServerSocket] is not possible and an exception 30 | /// will be thrown when calling this method. 31 | @override 32 | Future close() async { 33 | throw Exception('Did not expect close() to be called.'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/src/async_utils/async_utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:typed_data'; 7 | 8 | /// An interface for `StreamSink`-like classes to indicate whether adding data 9 | /// would be buffered and when the buffer is empty again. 10 | class BufferIndicator { 11 | final StreamController _controller = StreamController.broadcast(sync: true); 12 | 13 | /// A state variable indicating whether buffereing would occur at the moment. 14 | bool _wouldBuffer = true; 15 | 16 | /// Indicates whether calling [BufferedBytesWriter.add] would buffer the data 17 | /// if called. 18 | /// 19 | /// This can be used at a higher level as a way to do custom buffering and 20 | /// possibly prioritization. 21 | bool get wouldBuffer { 22 | return _wouldBuffer; 23 | } 24 | 25 | /// Signals that no buffering is happening at the moment. 26 | void markUnBuffered() { 27 | if (_wouldBuffer) { 28 | _wouldBuffer = false; 29 | _controller.add(null); 30 | } 31 | } 32 | 33 | /// Signals that buffering starts to happen. 34 | void markBuffered() { 35 | _wouldBuffer = true; 36 | } 37 | 38 | /// A broadcast stream notifying users that the [BufferedBytesWriter.add] 39 | /// method would not buffer the data if called. 40 | Stream get bufferEmptyEvents => _controller.stream; 41 | } 42 | 43 | /// Contains a [StreamSink] and a [BufferIndicator] to indicate whether writes 44 | /// to the sink would cause buffering. 45 | /// 46 | /// It uses the `pause signal` from the `sink.addStream()` as an indicator 47 | /// whether the underlying stream cannot handle more data and would buffer. 48 | class BufferedSink { 49 | /// The indicator whether the underlying sink is buffering at the moment. 50 | final bufferIndicator = BufferIndicator(); 51 | 52 | /// A intermediate [StreamController] used to catch pause signals and to 53 | /// propagate the change via [bufferIndicator]. 54 | final _controller = StreamController>(sync: true); 55 | 56 | /// A future which completes once the sink has been closed. 57 | late final Future _doneFuture; 58 | 59 | BufferedSink(StreamSink> dataSink) { 60 | bufferIndicator.markBuffered(); 61 | 62 | _controller 63 | ..onListen = bufferIndicator.markUnBuffered 64 | ..onPause = bufferIndicator.markBuffered 65 | ..onResume = bufferIndicator.markUnBuffered 66 | ..onCancel = () { 67 | // TODO: We may want to propagate cancel events as errors. 68 | // Currently `_doneFuture` will just complete normally if the sink 69 | // cancelled. 70 | }; 71 | _doneFuture = 72 | Future.wait([_controller.stream.pipe(dataSink), dataSink.done]); 73 | } 74 | 75 | /// The underlying sink. 76 | StreamSink> get sink => _controller; 77 | 78 | /// The future which will complete once this sink has been closed. 79 | Future get doneFuture => _doneFuture; 80 | } 81 | 82 | /// A small wrapper around [BufferedSink] which writes data in batches. 83 | class BufferedBytesWriter { 84 | /// A buffer which will be used for batching writes. 85 | final BytesBuilder _builder = BytesBuilder(copy: false); 86 | 87 | /// The underlying [BufferedSink]. 88 | final BufferedSink _bufferedSink; 89 | 90 | BufferedBytesWriter(StreamSink> outgoing) 91 | : _bufferedSink = BufferedSink(outgoing); 92 | 93 | /// An indicator whether the underlying sink is buffering at the moment. 94 | BufferIndicator get bufferIndicator => _bufferedSink.bufferIndicator; 95 | 96 | /// Adds [data] immediately to the underlying buffer. 97 | /// 98 | /// If there is buffered data which was added with [addBufferedData] and it 99 | /// has not been flushed with [flushBufferedData] an error will be thrown. 100 | void add(List data) { 101 | if (_builder.length > 0) { 102 | throw StateError( 103 | 'Cannot trigger an asynchronous write while there is buffered data.'); 104 | } 105 | _bufferedSink.sink.add(data); 106 | } 107 | 108 | /// Queues up [bytes] to be written. 109 | void addBufferedData(List bytes) { 110 | _builder.add(bytes); 111 | } 112 | 113 | /// Flushes all data which was enqueued by [addBufferedData]. 114 | void flushBufferedData() { 115 | if (_builder.length > 0) { 116 | _bufferedSink.sink.add(_builder.takeBytes()); 117 | } 118 | } 119 | 120 | /// Closes this sink. 121 | Future close() { 122 | flushBufferedData(); 123 | return _bufferedSink.sink.close().whenComplete(() => doneFuture); 124 | } 125 | 126 | /// The future which will complete once this sink has been closed. 127 | Future get doneFuture => _bufferedSink.doneFuture; 128 | } 129 | -------------------------------------------------------------------------------- /lib/src/byte_utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:typed_data'; 6 | 7 | List viewOrSublist(List data, int offset, int length) { 8 | if (data is Uint8List) { 9 | return Uint8List.view(data.buffer, data.offsetInBytes + offset, length); 10 | } else { 11 | return data.sublist(offset, offset + length); 12 | } 13 | } 14 | 15 | int readInt64(List bytes, int offset) { 16 | var high = readInt32(bytes, offset); 17 | var low = readInt32(bytes, offset + 4); 18 | return high << 32 | low; 19 | } 20 | 21 | int readInt32(List bytes, int offset) { 22 | return (bytes[offset] << 24) | 23 | (bytes[offset + 1] << 16) | 24 | (bytes[offset + 2] << 8) | 25 | bytes[offset + 3]; 26 | } 27 | 28 | int readInt24(List bytes, int offset) { 29 | return (bytes[offset] << 16) | (bytes[offset + 1] << 8) | bytes[offset + 2]; 30 | } 31 | 32 | int readInt16(List bytes, int offset) { 33 | return (bytes[offset] << 8) | bytes[offset + 1]; 34 | } 35 | 36 | void setInt64(List bytes, int offset, int value) { 37 | setInt32(bytes, offset, value >> 32); 38 | setInt32(bytes, offset + 4, value & 0xffffffff); 39 | } 40 | 41 | void setInt32(List bytes, int offset, int value) { 42 | bytes[offset] = (value >> 24) & 0xff; 43 | bytes[offset + 1] = (value >> 16) & 0xff; 44 | bytes[offset + 2] = (value >> 8) & 0xff; 45 | bytes[offset + 3] = value & 0xff; 46 | } 47 | 48 | void setInt24(List bytes, int offset, int value) { 49 | bytes[offset] = (value >> 16) & 0xff; 50 | bytes[offset + 1] = (value >> 8) & 0xff; 51 | bytes[offset + 2] = value & 0xff; 52 | } 53 | 54 | void setInt16(List bytes, int offset, int value) { 55 | bytes[offset] = (value >> 8) & 0xff; 56 | bytes[offset + 1] = value & 0xff; 57 | } 58 | -------------------------------------------------------------------------------- /lib/src/connection_preface.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:math'; 7 | 8 | import 'byte_utils.dart'; 9 | 10 | /// This is a set of bytes with which a client connection begins in the normal 11 | /// case. It can be used on a server to distinguish HTTP/1.1 and HTTP/2 clients. 12 | const List CONNECTION_PREFACE = [ 13 | 0x50, 14 | 0x52, 15 | 0x49, 16 | 0x20, 17 | 0x2a, 18 | 0x20, 19 | 0x48, 20 | 0x54, 21 | 0x54, 22 | 0x50, 23 | 0x2f, 24 | 0x32, 25 | 0x2e, 26 | 0x30, 27 | 0x0d, 28 | 0x0a, 29 | 0x0d, 30 | 0x0a, 31 | 0x53, 32 | 0x4d, 33 | 0x0d, 34 | 0x0a, 35 | 0x0d, 36 | 0x0a 37 | ]; 38 | 39 | /// Reads the connection preface from [incoming]. 40 | /// 41 | /// The returned `Stream` will be a duplicate of `incoming` without the 42 | /// connection preface. If an error occurs while reading the connection 43 | /// preface, the returned stream will have only an error. 44 | Stream> readConnectionPreface(Stream> incoming) { 45 | final result = StreamController>(); 46 | late StreamSubscription subscription; 47 | var connectionPrefaceRead = false; 48 | var prefaceBuffer = []; 49 | var terminated = false; 50 | 51 | void terminate(Object error) { 52 | if (!terminated) { 53 | result.addError(error); 54 | result.close(); 55 | subscription.cancel(); 56 | } 57 | terminated = true; 58 | } 59 | 60 | bool compareConnectionPreface(List data) { 61 | for (var i = 0; i < CONNECTION_PREFACE.length; i++) { 62 | if (data[i] != CONNECTION_PREFACE[i]) { 63 | terminate('Connection preface does not match.'); 64 | return false; 65 | } 66 | } 67 | connectionPrefaceRead = true; 68 | return true; 69 | } 70 | 71 | void onData(List data) { 72 | if (connectionPrefaceRead) { 73 | // Forward data after reading preface. 74 | result.add(data); 75 | } else { 76 | if (prefaceBuffer.isEmpty && data.length > CONNECTION_PREFACE.length) { 77 | if (!compareConnectionPreface(data)) return; 78 | data = data.sublist(CONNECTION_PREFACE.length); 79 | } else if (prefaceBuffer.length < CONNECTION_PREFACE.length) { 80 | var remaining = CONNECTION_PREFACE.length - prefaceBuffer.length; 81 | 82 | var end = min(data.length, remaining); 83 | var part1 = viewOrSublist(data, 0, end); 84 | var part2 = viewOrSublist(data, end, data.length - end); 85 | prefaceBuffer.addAll(part1); 86 | 87 | if (prefaceBuffer.length == CONNECTION_PREFACE.length) { 88 | if (!compareConnectionPreface(prefaceBuffer)) return; 89 | } 90 | data = part2; 91 | } 92 | if (data.isNotEmpty) { 93 | result.add(data); 94 | } 95 | } 96 | } 97 | 98 | result.onListen = () { 99 | subscription = 100 | incoming.listen(onData, onError: result.addError, onDone: () { 101 | if (!connectionPrefaceRead) { 102 | terminate('EOS before connection preface could be read.'); 103 | } else { 104 | result.close(); 105 | } 106 | }); 107 | result 108 | ..onPause = subscription.pause 109 | ..onResume = subscription.resume 110 | ..onCancel = subscription.cancel; 111 | }; 112 | 113 | return result.stream; 114 | } 115 | -------------------------------------------------------------------------------- /lib/src/error_handler.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'sync_errors.dart'; 8 | 9 | /// Used by classes which may be terminated from the outside. 10 | mixin TerminatableMixin { 11 | bool _terminated = false; 12 | 13 | /// Terminates this stream message queue. Further operations on it will fail. 14 | void terminate([Object? error]) { 15 | if (!wasTerminated) { 16 | _terminated = true; 17 | onTerminated(error); 18 | } 19 | } 20 | 21 | bool get wasTerminated => _terminated; 22 | 23 | void onTerminated(Object? error) { 24 | // Subclasses can override this method if they want. 25 | } 26 | 27 | T ensureNotTerminatedSync(T Function() f) { 28 | if (wasTerminated) { 29 | throw TerminatedException(); 30 | } 31 | return f(); 32 | } 33 | 34 | Future ensureNotTerminatedAsync(Future Function() f) { 35 | if (wasTerminated) { 36 | return Future.error(TerminatedException()); 37 | } 38 | return f(); 39 | } 40 | } 41 | 42 | /// Used by classes which may be cancelled. 43 | mixin CancellableMixin { 44 | bool _cancelled = false; 45 | final _cancelCompleter = Completer.sync(); 46 | 47 | Future get onCancel => _cancelCompleter.future; 48 | 49 | /// Cancel this stream message queue. Further operations on it will fail. 50 | void cancel() { 51 | if (!wasCancelled) { 52 | _cancelled = true; 53 | _cancelCompleter.complete(); 54 | } 55 | } 56 | 57 | bool get wasCancelled => _cancelled; 58 | } 59 | 60 | /// Used by classes which may be closed. 61 | mixin ClosableMixin { 62 | bool _closing = false; 63 | final Completer _completer = Completer(); 64 | 65 | Future get done => _completer.future; 66 | 67 | bool get isClosing => _closing; 68 | bool get wasClosed => _completer.isCompleted; 69 | 70 | void startClosing() { 71 | if (!_closing) { 72 | _closing = true; 73 | 74 | onClosing(); 75 | } 76 | onCheckForClose(); 77 | } 78 | 79 | void onCheckForClose() { 80 | // Subclasses can override this method if they want. 81 | } 82 | 83 | void onClosing() { 84 | // Subclasses can override this method if they want. 85 | } 86 | 87 | dynamic ensureNotClosingSync(dynamic Function() f) { 88 | if (isClosing) { 89 | throw StateError('Was in the process of closing.'); 90 | } 91 | return f(); 92 | } 93 | 94 | void closeWithValue([Object? value]) { 95 | if (!wasClosed) { 96 | _completer.complete(value); 97 | } 98 | } 99 | 100 | void closeWithError(Object? error) { 101 | if (!wasClosed) { 102 | _completer.complete(error); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /lib/src/flowcontrol/queue_messages.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import '../../transport.dart'; 6 | 7 | /// The subclasses of [Message] are objects that are coming from the 8 | /// connection layer on top of frames. 9 | /// 10 | /// Messages on a HTTP/2 stream will be represented by a different class 11 | /// hierarchy. 12 | abstract class Message { 13 | final int streamId; 14 | final bool endStream; 15 | 16 | Message(this.streamId, this.endStream); 17 | } 18 | 19 | class HeadersMessage extends Message { 20 | final List
headers; 21 | 22 | HeadersMessage(int streamId, this.headers, bool endStream) 23 | : super(streamId, endStream); 24 | 25 | @override 26 | String toString() => 27 | 'HeadersMessage(headers: ${headers.length}, endStream: $endStream)'; 28 | } 29 | 30 | class DataMessage extends Message { 31 | final List bytes; 32 | 33 | DataMessage(int streamId, this.bytes, bool endStream) 34 | : super(streamId, endStream); 35 | 36 | @override 37 | String toString() => 38 | 'DataMessage(bytes: ${bytes.length}, endStream: $endStream)'; 39 | } 40 | 41 | class PushPromiseMessage extends Message { 42 | final List
headers; 43 | final int promisedStreamId; 44 | final ClientTransportStream pushedStream; 45 | 46 | PushPromiseMessage(int streamId, this.headers, this.promisedStreamId, 47 | this.pushedStream, bool endStream) 48 | : super(streamId, endStream); 49 | 50 | @override 51 | String toString() => 'PushPromiseMessage(bytes: ${headers.length}, ' 52 | 'promisedStreamId: $promisedStreamId, endStream: $endStream)'; 53 | } 54 | 55 | class ResetStreamMessage extends Message { 56 | final int errorCode; 57 | 58 | ResetStreamMessage(int streamId, this.errorCode) : super(streamId, false); 59 | 60 | @override 61 | String toString() => 'ResetStreamMessage(errorCode: $errorCode)'; 62 | } 63 | 64 | class GoawayMessage extends Message { 65 | final int lastStreamId; 66 | final int errorCode; 67 | final List debugData; 68 | 69 | GoawayMessage(this.lastStreamId, this.errorCode, this.debugData) 70 | : super(0, false); 71 | 72 | @override 73 | String toString() => 'GoawayMessage(lastStreamId: $lastStreamId, ' 74 | 'errorCode: $errorCode, debugData: ${debugData.length})'; 75 | } 76 | -------------------------------------------------------------------------------- /lib/src/flowcontrol/window.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | class Window { 6 | static const int MAX_WINDOW_SIZE = (1 << 31) - 1; 7 | 8 | /// The size available in this window. 9 | /// 10 | /// The default flow control window for the entire connection and for new 11 | /// streams is 65535). 12 | /// 13 | /// NOTE: This value can potentially become negative. 14 | int _size; 15 | 16 | Window({int initialSize = (1 << 16) - 1}) : _size = initialSize; 17 | 18 | /// The current size of the flow control window. 19 | int get size => _size; 20 | 21 | void modify(int difference) { 22 | _size += difference; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/src/flowcontrol/window_handler.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import '../async_utils/async_utils.dart'; 6 | import '../frames/frames.dart'; 7 | import '../sync_errors.dart'; 8 | 9 | import 'window.dart'; 10 | 11 | abstract class AbstractOutgoingWindowHandler { 12 | /// The connection flow control window. 13 | final Window _peerWindow; 14 | 15 | /// Indicates when the outgoing connection window turned positive and we can 16 | /// send data frames again. 17 | final BufferIndicator positiveWindow = BufferIndicator(); 18 | 19 | AbstractOutgoingWindowHandler(this._peerWindow) { 20 | if (_peerWindow.size > 0) { 21 | positiveWindow.markUnBuffered(); 22 | } 23 | } 24 | 25 | /// The flow control window size we use for sending data. We are not allowed 26 | /// to let this window be negative. 27 | int get peerWindowSize => _peerWindow.size; 28 | 29 | /// Process a window update frame received from the remote end. 30 | void processWindowUpdate(WindowUpdateFrame frame) { 31 | var increment = frame.windowSizeIncrement; 32 | if ((_peerWindow.size + increment) > Window.MAX_WINDOW_SIZE) { 33 | throw FlowControlException( 34 | 'Window update received from remote peer would make flow control ' 35 | 'window too large.'); 36 | } else { 37 | _peerWindow.modify(increment); 38 | } 39 | 40 | // If we transitioned from an negative/empty window to a positive window 41 | // we'll fire an event that more data frames can be sent now. 42 | if (positiveWindow.wouldBuffer && _peerWindow.size > 0) { 43 | positiveWindow.markUnBuffered(); 44 | } 45 | } 46 | 47 | /// Update the peer window by subtracting [numberOfBytes]. 48 | /// 49 | /// The remote peer will send us [WindowUpdateFrame]s which will increase 50 | /// the window again at a later point in time. 51 | void decreaseWindow(int numberOfBytes) { 52 | _peerWindow.modify(-numberOfBytes); 53 | if (_peerWindow.size <= 0) { 54 | positiveWindow.markBuffered(); 55 | } 56 | } 57 | } 58 | 59 | /// Handles the connection window for outgoing data frames. 60 | class OutgoingConnectionWindowHandler extends AbstractOutgoingWindowHandler { 61 | OutgoingConnectionWindowHandler(super.window); 62 | } 63 | 64 | /// Handles the window for outgoing messages to the peer. 65 | class OutgoingStreamWindowHandler extends AbstractOutgoingWindowHandler { 66 | OutgoingStreamWindowHandler(super.window); 67 | 68 | /// Update the peer window by adding [difference] to it. 69 | /// 70 | /// 71 | /// The remote peer has send a new [SettingsFrame] which updated the default 72 | /// stream level [Setting.SETTINGS_INITIAL_WINDOW_SIZE]. This causes all 73 | /// existing streams to update the flow stream-level flow control window. 74 | void processInitialWindowSizeSettingChange(int difference) { 75 | if ((_peerWindow.size + difference) > Window.MAX_WINDOW_SIZE) { 76 | throw FlowControlException( 77 | 'Window update received from remote peer would make flow control ' 78 | 'window too large.'); 79 | } else { 80 | _peerWindow.modify(difference); 81 | if (_peerWindow.size <= 0) { 82 | positiveWindow.markBuffered(); 83 | } else if (positiveWindow.wouldBuffer) { 84 | positiveWindow.markUnBuffered(); 85 | } 86 | } 87 | } 88 | } 89 | 90 | /// Mirrors the flow control window the remote end is using. 91 | class IncomingWindowHandler { 92 | /// The [FrameWriter] used for writing [WindowUpdateFrame]s to the wire. 93 | final FrameWriter _frameWriter; 94 | 95 | /// The mirror of the [Window] the remote end sees. 96 | /// 97 | /// If [_localWindow ] turns negative, it means the remote peer sent us more 98 | /// data than we allowed it to send. 99 | final Window _localWindow; 100 | 101 | /// The stream id this window handler is for (is `0` for connection level). 102 | final int _streamId; 103 | 104 | IncomingWindowHandler.stream( 105 | this._frameWriter, this._localWindow, this._streamId); 106 | 107 | IncomingWindowHandler.connection(this._frameWriter, this._localWindow) 108 | : _streamId = 0; 109 | 110 | /// The current size for the incoming data window. 111 | /// 112 | /// (This should never get negative, otherwise the peer send us more data 113 | /// than we told it to send.) 114 | int get localWindowSize => _localWindow.size; 115 | 116 | /// Signals that we received [numberOfBytes] from the remote peer. 117 | void gotData(int numberOfBytes) { 118 | _localWindow.modify(-numberOfBytes); 119 | 120 | // If this turns negative, it means the remote end send us more data 121 | // then we announced we can handle (i.e. the remote window size must be 122 | // negative). 123 | // 124 | // NOTE: [_localWindow.size] tracks the amount of data we advertised that we 125 | // can handle. The value can change in three situations: 126 | // 127 | // a) We received data from the remote end (we can handle now less data) 128 | // => This is handled by [gotData]. 129 | // 130 | // b) We processed data from the remote end (we can handle now more data) 131 | // => This is handled by [dataProcessed]. 132 | // 133 | // c) We increase/decrease the initial stream window size after the 134 | // stream was created (newer streams will start with the changed 135 | // initial stream window size). 136 | // => This is not an issue, because we don't support changing the 137 | // initial window size later on -- only during the initial 138 | // settings exchange. Since streams (and therefore instances 139 | // of [IncomingWindowHandler]) are only created after sending out 140 | // our initial settings. 141 | // 142 | if (_localWindow.size < 0) { 143 | throw FlowControlException( 144 | 'Connection level flow control window became negative.'); 145 | } 146 | } 147 | 148 | /// Tell the peer we received [numberOfBytes] bytes. It will increase it's 149 | /// sending window then. 150 | /// 151 | // TODO/FIXME: If we pause and don't want to get more data, we have to 152 | // - either stop sending window update frames 153 | // - or decreasing the window size 154 | void dataProcessed(int numberOfBytes) { 155 | _localWindow.modify(numberOfBytes); 156 | 157 | // TODO: This can be optimized by delaying the window update to 158 | // send one update with a bigger difference than multiple small update 159 | // frames. 160 | _frameWriter.writeWindowUpdate(numberOfBytes, streamId: _streamId); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /lib/src/frames/frame_defragmenter.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import '../sync_errors.dart'; 6 | 7 | import 'frames.dart'; 8 | 9 | /// Class used for defragmenting [HeadersFrame]s and [PushPromiseFrame]s. 10 | // TODO: Somehow emit an error if too many continuation frames have been sent 11 | // (since we're buffering all of them). 12 | class FrameDefragmenter { 13 | /// The current incomplete [HeadersFrame] fragment. 14 | HeadersFrame? _headersFrame; 15 | 16 | /// The current incomplete [PushPromiseFrame] fragment. 17 | PushPromiseFrame? _pushPromiseFrame; 18 | 19 | /// Tries to defragment [frame]. 20 | /// 21 | /// If the given [frame] is a [HeadersFrame] or a [PushPromiseFrame] which 22 | /// needs de-fragmentation, it will be saved and `null` will be returned. 23 | /// 24 | /// If there is currently an incomplete [HeadersFrame] or [PushPromiseFrame] 25 | /// saved, [frame] needs to be a [ContinuationFrame]. It will be added to the 26 | /// saved frame. In case the defragmentation is complete, the defragmented 27 | /// [HeadersFrame] or [PushPromiseFrame] will be returned. 28 | /// 29 | /// All other [Frame] types will be returned. 30 | // TODO: Consider handling continuation frames without preceding 31 | // headers/push-promise frame here instead of the call site? 32 | Frame? tryDefragmentFrame(Frame? frame) { 33 | if (_headersFrame != null) { 34 | if (frame is ContinuationFrame) { 35 | if (_headersFrame!.header.streamId != frame.header.streamId) { 36 | throw ProtocolException( 37 | 'Defragmentation: frames have different stream ids.'); 38 | } 39 | _headersFrame = _headersFrame!.addBlockContinuation(frame); 40 | 41 | if (frame.hasEndHeadersFlag) { 42 | var frame = _headersFrame; 43 | _headersFrame = null; 44 | return frame; 45 | } else { 46 | return null; 47 | } 48 | } else { 49 | throw ProtocolException( 50 | 'Defragmentation: Incomplete frame must be followed by ' 51 | 'continuation frame.'); 52 | } 53 | } else if (_pushPromiseFrame != null) { 54 | if (frame is ContinuationFrame) { 55 | if (_pushPromiseFrame!.header.streamId != frame.header.streamId) { 56 | throw ProtocolException( 57 | 'Defragmentation: frames have different stream ids.'); 58 | } 59 | _pushPromiseFrame = _pushPromiseFrame!.addBlockContinuation(frame); 60 | 61 | if (frame.hasEndHeadersFlag) { 62 | var frame = _pushPromiseFrame; 63 | _pushPromiseFrame = null; 64 | return frame; 65 | } else { 66 | return null; 67 | } 68 | } else { 69 | throw ProtocolException( 70 | 'Defragmentation: Incomplete frame must be followed by ' 71 | 'continuation frame.'); 72 | } 73 | } else { 74 | if (frame is HeadersFrame) { 75 | if (!frame.hasEndHeadersFlag) { 76 | _headersFrame = frame; 77 | return null; 78 | } 79 | } else if (frame is PushPromiseFrame) { 80 | if (!frame.hasEndHeadersFlag) { 81 | _pushPromiseFrame = frame; 82 | return null; 83 | } 84 | } 85 | } 86 | 87 | // If this frame is not relevant for header defragmentation, we pass it to 88 | // the next stage. 89 | return frame; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /lib/src/frames/frame_types.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | part of 'frames.dart'; 6 | 7 | const int FRAME_HEADER_SIZE = 9; 8 | 9 | class FrameType { 10 | static const int DATA = 0; 11 | static const int HEADERS = 1; 12 | static const int PRIORITY = 2; 13 | static const int RST_STREAM = 3; 14 | static const int SETTINGS = 4; 15 | static const int PUSH_PROMISE = 5; 16 | static const int PING = 6; 17 | static const int GOAWAY = 7; 18 | static const int WINDOW_UPDATE = 8; 19 | static const int CONTINUATION = 9; 20 | } 21 | 22 | class ErrorCode { 23 | static const int NO_ERROR = 0; 24 | static const int PROTOCOL_ERROR = 1; 25 | static const int INTERNAL_ERROR = 2; 26 | static const int FLOW_CONTROL_ERROR = 3; 27 | static const int SETTINGS_TIMEOUT = 4; 28 | static const int STREAM_CLOSED = 5; 29 | static const int FRAME_SIZE_ERROR = 6; 30 | static const int REFUSED_STREAM = 7; 31 | static const int CANCEL = 8; 32 | static const int COMPRESSION_ERROR = 9; 33 | static const int CONNECT_ERROR = 10; 34 | static const int ENHANCE_YOUR_CALM = 11; 35 | static const int INADEQUATE_SECURITY = 12; 36 | static const int HTTP_1_1_REQUIRED = 13; 37 | } 38 | 39 | class FrameHeader { 40 | final int length; 41 | final int type; 42 | final int flags; 43 | final int streamId; 44 | 45 | FrameHeader(this.length, this.type, this.flags, this.streamId); 46 | 47 | Map toJson() => 48 | {'length': length, 'type': type, 'flags': flags, 'streamId': streamId}; 49 | } 50 | 51 | class Frame { 52 | static const int MAX_LEN = (1 << 24) - 1; 53 | 54 | final FrameHeader header; 55 | 56 | Frame(this.header); 57 | 58 | Map toJson() => {'header': header.toJson()}; 59 | } 60 | 61 | class DataFrame extends Frame { 62 | static const int FLAG_END_STREAM = 0x1; 63 | static const int FLAG_PADDED = 0x8; 64 | 65 | /// The number of padding bytes. 66 | final int padLength; 67 | 68 | final List bytes; 69 | 70 | DataFrame(super.header, this.padLength, this.bytes); 71 | 72 | bool get hasEndStreamFlag => _isFlagSet(header.flags, FLAG_END_STREAM); 73 | bool get hasPaddedFlag => _isFlagSet(header.flags, FLAG_PADDED); 74 | 75 | @override 76 | Map toJson() => super.toJson() 77 | ..addAll({ 78 | 'padLength': padLength, 79 | 'bytes (length)': bytes.length, 80 | 'bytes (up to 4 bytes)': bytes.length > 4 ? bytes.sublist(0, 4) : bytes, 81 | }); 82 | } 83 | 84 | class HeadersFrame extends Frame { 85 | static const int FLAG_END_STREAM = 0x1; 86 | static const int FLAG_END_HEADERS = 0x4; 87 | static const int FLAG_PADDED = 0x8; 88 | static const int FLAG_PRIORITY = 0x20; 89 | 90 | // NOTE: This is the size a [HeadersFrame] can have in addition to padding 91 | // and header block fragment data. 92 | static const int MAX_CONSTANT_PAYLOAD = 6; 93 | 94 | /// The number of padding bytes (might be null). 95 | final int padLength; 96 | 97 | final bool exclusiveDependency; 98 | final int? streamDependency; 99 | final int? weight; 100 | final List headerBlockFragment; 101 | 102 | HeadersFrame( 103 | super.header, 104 | this.padLength, 105 | this.exclusiveDependency, 106 | this.streamDependency, 107 | this.weight, 108 | this.headerBlockFragment, 109 | ); 110 | 111 | /// This will be set from the outside after decoding. 112 | late List
decodedHeaders; 113 | 114 | bool get hasEndStreamFlag => _isFlagSet(header.flags, FLAG_END_STREAM); 115 | bool get hasEndHeadersFlag => _isFlagSet(header.flags, FLAG_END_HEADERS); 116 | bool get hasPaddedFlag => _isFlagSet(header.flags, FLAG_PADDED); 117 | bool get hasPriorityFlag => _isFlagSet(header.flags, FLAG_PRIORITY); 118 | 119 | HeadersFrame addBlockContinuation(ContinuationFrame frame) { 120 | var fragment = frame.headerBlockFragment; 121 | var flags = header.flags | frame.header.flags; 122 | var fh = FrameHeader( 123 | header.length + fragment.length, header.type, flags, header.streamId); 124 | 125 | var mergedHeaderBlockFragment = 126 | Uint8List(headerBlockFragment.length + fragment.length); 127 | 128 | mergedHeaderBlockFragment.setRange( 129 | 0, headerBlockFragment.length, headerBlockFragment); 130 | 131 | mergedHeaderBlockFragment.setRange( 132 | headerBlockFragment.length, mergedHeaderBlockFragment.length, fragment); 133 | 134 | return HeadersFrame(fh, padLength, exclusiveDependency, streamDependency, 135 | weight, mergedHeaderBlockFragment); 136 | } 137 | 138 | @override 139 | Map toJson() => super.toJson() 140 | ..addAll({ 141 | 'padLength': padLength, 142 | 'exclusiveDependency': exclusiveDependency, 143 | 'streamDependency': streamDependency, 144 | 'weight': weight, 145 | 'headerBlockFragment (length)': headerBlockFragment.length 146 | }); 147 | } 148 | 149 | class PriorityFrame extends Frame { 150 | static const int FIXED_FRAME_LENGTH = 5; 151 | 152 | final bool exclusiveDependency; 153 | final int streamDependency; 154 | final int weight; 155 | 156 | PriorityFrame( 157 | super.header, 158 | this.exclusiveDependency, 159 | this.streamDependency, 160 | this.weight, 161 | ); 162 | 163 | @override 164 | Map toJson() => super.toJson() 165 | ..addAll({ 166 | 'exclusiveDependency': exclusiveDependency, 167 | 'streamDependency': streamDependency, 168 | 'weight': weight, 169 | }); 170 | } 171 | 172 | class RstStreamFrame extends Frame { 173 | static const int FIXED_FRAME_LENGTH = 4; 174 | 175 | final int errorCode; 176 | 177 | RstStreamFrame(super.header, this.errorCode); 178 | 179 | @override 180 | Map toJson() => super.toJson() 181 | ..addAll({ 182 | 'errorCode': errorCode, 183 | }); 184 | } 185 | 186 | class Setting { 187 | static const int SETTINGS_HEADER_TABLE_SIZE = 1; 188 | static const int SETTINGS_ENABLE_PUSH = 2; 189 | static const int SETTINGS_MAX_CONCURRENT_STREAMS = 3; 190 | static const int SETTINGS_INITIAL_WINDOW_SIZE = 4; 191 | static const int SETTINGS_MAX_FRAME_SIZE = 5; 192 | static const int SETTINGS_MAX_HEADER_LIST_SIZE = 6; 193 | 194 | final int identifier; 195 | final int value; 196 | 197 | Setting(this.identifier, this.value); 198 | 199 | Map toJson() => {'identifier': identifier, 'value': value}; 200 | } 201 | 202 | class SettingsFrame extends Frame { 203 | static const int FLAG_ACK = 0x1; 204 | 205 | // A setting consist of a 2 byte identifier and a 4 byte value. 206 | static const int SETTING_SIZE = 6; 207 | 208 | final List settings; 209 | 210 | SettingsFrame(super.header, this.settings); 211 | 212 | bool get hasAckFlag => _isFlagSet(header.flags, FLAG_ACK); 213 | 214 | @override 215 | Map toJson() => super.toJson() 216 | ..addAll({ 217 | 'settings': settings.map((s) => s.toJson()).toList(), 218 | }); 219 | } 220 | 221 | class PushPromiseFrame extends Frame { 222 | static const int FLAG_END_HEADERS = 0x4; 223 | static const int FLAG_PADDED = 0x8; 224 | 225 | // NOTE: This is the size a [PushPromiseFrame] can have in addition to padding 226 | // and header block fragment data. 227 | static const int MAX_CONSTANT_PAYLOAD = 5; 228 | 229 | final int padLength; 230 | final int promisedStreamId; 231 | final List headerBlockFragment; 232 | 233 | /// This will be set from the outside after decoding. 234 | late List
decodedHeaders; 235 | 236 | PushPromiseFrame( 237 | super.header, 238 | this.padLength, 239 | this.promisedStreamId, 240 | this.headerBlockFragment, 241 | ); 242 | 243 | bool get hasEndHeadersFlag => _isFlagSet(header.flags, FLAG_END_HEADERS); 244 | bool get hasPaddedFlag => _isFlagSet(header.flags, FLAG_PADDED); 245 | 246 | PushPromiseFrame addBlockContinuation(ContinuationFrame frame) { 247 | var fragment = frame.headerBlockFragment; 248 | var flags = header.flags | frame.header.flags; 249 | var fh = FrameHeader( 250 | header.length + fragment.length, header.type, flags, header.streamId); 251 | 252 | var mergedHeaderBlockFragment = 253 | Uint8List(headerBlockFragment.length + fragment.length); 254 | 255 | mergedHeaderBlockFragment.setRange( 256 | 0, headerBlockFragment.length, headerBlockFragment); 257 | 258 | mergedHeaderBlockFragment.setRange( 259 | headerBlockFragment.length, mergedHeaderBlockFragment.length, fragment); 260 | 261 | return PushPromiseFrame( 262 | fh, padLength, promisedStreamId, mergedHeaderBlockFragment); 263 | } 264 | 265 | @override 266 | Map toJson() => super.toJson() 267 | ..addAll({ 268 | 'padLength': padLength, 269 | 'promisedStreamId': promisedStreamId, 270 | 'headerBlockFragment (len)': headerBlockFragment.length, 271 | }); 272 | } 273 | 274 | class PingFrame extends Frame { 275 | static const int FIXED_FRAME_LENGTH = 8; 276 | 277 | static const int FLAG_ACK = 0x1; 278 | 279 | final int opaqueData; 280 | 281 | PingFrame(super.header, this.opaqueData); 282 | 283 | bool get hasAckFlag => _isFlagSet(header.flags, FLAG_ACK); 284 | 285 | @override 286 | Map toJson() => super.toJson() 287 | ..addAll({ 288 | 'opaqueData': opaqueData, 289 | }); 290 | } 291 | 292 | class GoawayFrame extends Frame { 293 | final int lastStreamId; 294 | final int errorCode; 295 | final List debugData; 296 | 297 | GoawayFrame(super.header, this.lastStreamId, this.errorCode, this.debugData); 298 | 299 | @override 300 | Map toJson() => super.toJson() 301 | ..addAll({ 302 | 'lastStreamId': lastStreamId, 303 | 'errorCode': errorCode, 304 | 'debugData (length)': debugData.length, 305 | }); 306 | } 307 | 308 | class WindowUpdateFrame extends Frame { 309 | static const int FIXED_FRAME_LENGTH = 4; 310 | 311 | final int windowSizeIncrement; 312 | 313 | WindowUpdateFrame(super.header, this.windowSizeIncrement); 314 | 315 | @override 316 | Map toJson() => super.toJson() 317 | ..addAll({ 318 | 'windowSizeIncrement': windowSizeIncrement, 319 | }); 320 | } 321 | 322 | class ContinuationFrame extends Frame { 323 | static const int FLAG_END_HEADERS = 0x4; 324 | 325 | final List headerBlockFragment; 326 | 327 | ContinuationFrame(super.header, this.headerBlockFragment); 328 | 329 | bool get hasEndHeadersFlag => _isFlagSet(header.flags, FLAG_END_HEADERS); 330 | 331 | @override 332 | Map toJson() => super.toJson() 333 | ..addAll({ 334 | 'headerBlockFragment (length)': headerBlockFragment.length, 335 | }); 336 | } 337 | 338 | class UnknownFrame extends Frame { 339 | final List data; 340 | 341 | UnknownFrame(super.header, this.data); 342 | 343 | @override 344 | Map toJson() => super.toJson() 345 | ..addAll({ 346 | 'data (length)': data.length, 347 | }); 348 | } 349 | -------------------------------------------------------------------------------- /lib/src/frames/frame_utils.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | part of 'frames.dart'; 6 | 7 | bool _isFlagSet(int value, int flag) => value & flag == flag; 8 | -------------------------------------------------------------------------------- /lib/src/frames/frame_writer.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | part of 'frames.dart'; 6 | 7 | // TODO: No support for writing padded information. 8 | // TODO: No support for stream priorities. 9 | class FrameWriter { 10 | /// The HPack compression context. 11 | final HPackEncoder _hpackEncoder; 12 | 13 | /// A buffered writer for outgoing bytes. 14 | final BufferedBytesWriter _outWriter; 15 | 16 | /// Connection settings which this writer needs to respect. 17 | final ActiveSettings _peerSettings; 18 | 19 | /// This is the maximum over all stream id's we've written to the underlying 20 | /// sink. 21 | int _highestWrittenStreamId = 0; 22 | 23 | FrameWriter( 24 | this._hpackEncoder, StreamSink> outgoing, this._peerSettings) 25 | : _outWriter = BufferedBytesWriter(outgoing); 26 | 27 | /// A indicator whether writes would be buffered. 28 | BufferIndicator get bufferIndicator => _outWriter.bufferIndicator; 29 | 30 | /// This is the maximum over all stream id's we've written to the underlying 31 | /// sink. 32 | int get highestWrittenStreamId => _highestWrittenStreamId; 33 | 34 | void writeDataFrame(int streamId, List data, {bool endStream = false}) { 35 | while (data.length > _peerSettings.maxFrameSize) { 36 | var chunk = viewOrSublist(data, 0, _peerSettings.maxFrameSize); 37 | data = viewOrSublist(data, _peerSettings.maxFrameSize, 38 | data.length - _peerSettings.maxFrameSize); 39 | _writeDataFrameNoFragment(streamId, chunk, false); 40 | } 41 | _writeDataFrameNoFragment(streamId, data, endStream); 42 | } 43 | 44 | void _writeDataFrameNoFragment(int streamId, List data, bool endStream) { 45 | var type = FrameType.DATA; 46 | var flags = endStream ? DataFrame.FLAG_END_STREAM : 0; 47 | 48 | var buffer = Uint8List(FRAME_HEADER_SIZE + data.length); 49 | var offset = 0; 50 | 51 | _setFrameHeader(buffer, offset, type, flags, streamId, data.length); 52 | offset += FRAME_HEADER_SIZE; 53 | 54 | buffer.setRange(offset, offset + data.length, data); 55 | 56 | _writeData(buffer); 57 | } 58 | 59 | void writeHeadersFrame(int streamId, List
headers, 60 | {bool endStream = true}) { 61 | var fragment = _hpackEncoder.encode(headers); 62 | var maxSize = 63 | _peerSettings.maxFrameSize - HeadersFrame.MAX_CONSTANT_PAYLOAD; 64 | 65 | if (fragment.length < maxSize) { 66 | _writeHeadersFrameNoFragment(streamId, fragment, true, endStream); 67 | } else { 68 | var chunk = fragment.sublist(0, maxSize); 69 | fragment = fragment.sublist(maxSize); 70 | _writeHeadersFrameNoFragment(streamId, chunk, false, endStream); 71 | while (fragment.length > _peerSettings.maxFrameSize) { 72 | var chunk = fragment.sublist(0, _peerSettings.maxFrameSize); 73 | fragment = fragment.sublist(_peerSettings.maxFrameSize); 74 | _writeContinuationFrame(streamId, chunk, false); 75 | } 76 | _writeContinuationFrame(streamId, fragment, true); 77 | } 78 | } 79 | 80 | void _writeHeadersFrameNoFragment( 81 | int streamId, List fragment, bool endHeaders, bool endStream) { 82 | var type = FrameType.HEADERS; 83 | var flags = 0; 84 | if (endHeaders) flags |= HeadersFrame.FLAG_END_HEADERS; 85 | if (endStream) flags |= HeadersFrame.FLAG_END_STREAM; 86 | 87 | var buffer = Uint8List(FRAME_HEADER_SIZE + fragment.length); 88 | var offset = 0; 89 | 90 | _setFrameHeader(buffer, offset, type, flags, streamId, fragment.length); 91 | offset += FRAME_HEADER_SIZE; 92 | 93 | buffer.setRange(offset, buffer.length, fragment); 94 | 95 | _writeData(buffer); 96 | } 97 | 98 | void _writeContinuationFrame( 99 | int streamId, List fragment, bool endHeaders) { 100 | var type = FrameType.CONTINUATION; 101 | var flags = endHeaders ? ContinuationFrame.FLAG_END_HEADERS : 0; 102 | 103 | var buffer = Uint8List(FRAME_HEADER_SIZE + fragment.length); 104 | var offset = 0; 105 | 106 | _setFrameHeader(buffer, offset, type, flags, streamId, fragment.length); 107 | offset += FRAME_HEADER_SIZE; 108 | 109 | buffer.setRange(offset, buffer.length, fragment); 110 | 111 | _writeData(buffer); 112 | } 113 | 114 | void writePriorityFrame(int streamId, int streamDependency, int weight, 115 | {bool exclusive = false}) { 116 | var type = FrameType.PRIORITY; 117 | var flags = 0; 118 | 119 | var buffer = 120 | Uint8List(FRAME_HEADER_SIZE + PriorityFrame.FIXED_FRAME_LENGTH); 121 | var offset = 0; 122 | 123 | _setFrameHeader(buffer, offset, type, flags, streamId, 5); 124 | offset += FRAME_HEADER_SIZE; 125 | 126 | if (exclusive) { 127 | setInt32(buffer, offset, (1 << 31) | streamDependency); 128 | } else { 129 | setInt32(buffer, offset, streamDependency); 130 | } 131 | buffer[offset + 4] = weight; 132 | 133 | _writeData(buffer); 134 | } 135 | 136 | void writeRstStreamFrame(int streamId, int errorCode) { 137 | var type = FrameType.RST_STREAM; 138 | var flags = 0; 139 | 140 | var buffer = 141 | Uint8List(FRAME_HEADER_SIZE + RstStreamFrame.FIXED_FRAME_LENGTH); 142 | var offset = 0; 143 | 144 | _setFrameHeader(buffer, offset, type, flags, streamId, 4); 145 | offset += FRAME_HEADER_SIZE; 146 | 147 | setInt32(buffer, offset, errorCode); 148 | 149 | _writeData(buffer); 150 | } 151 | 152 | void writeSettingsFrame(List settings) { 153 | var type = FrameType.SETTINGS; 154 | var flags = 0; 155 | 156 | var buffer = Uint8List(FRAME_HEADER_SIZE + 6 * settings.length); 157 | var offset = 0; 158 | 159 | _setFrameHeader(buffer, offset, type, flags, 0, 6 * settings.length); 160 | offset += FRAME_HEADER_SIZE; 161 | 162 | for (var i = 0; i < settings.length; i++) { 163 | var setting = settings[i]; 164 | setInt16(buffer, offset + 6 * i, setting.identifier); 165 | setInt32(buffer, offset + 6 * i + 2, setting.value); 166 | } 167 | 168 | _writeData(buffer); 169 | } 170 | 171 | void writeSettingsAckFrame() { 172 | var type = FrameType.SETTINGS; 173 | var flags = SettingsFrame.FLAG_ACK; 174 | 175 | var buffer = Uint8List(FRAME_HEADER_SIZE); 176 | var offset = 0; 177 | 178 | _setFrameHeader(buffer, offset, type, flags, 0, 0); 179 | offset += FRAME_HEADER_SIZE; 180 | 181 | _writeData(buffer); 182 | } 183 | 184 | void writePushPromiseFrame( 185 | int streamId, int promisedStreamId, List
headers) { 186 | var fragment = _hpackEncoder.encode(headers); 187 | var maxSize = 188 | _peerSettings.maxFrameSize - PushPromiseFrame.MAX_CONSTANT_PAYLOAD; 189 | 190 | if (fragment.length < maxSize) { 191 | _writePushPromiseFrameNoFragmentation( 192 | streamId, promisedStreamId, fragment, true); 193 | } else { 194 | var chunk = fragment.sublist(0, maxSize); 195 | fragment = fragment.sublist(maxSize); 196 | _writePushPromiseFrameNoFragmentation( 197 | streamId, promisedStreamId, chunk, false); 198 | while (fragment.length > _peerSettings.maxFrameSize) { 199 | var chunk = fragment.sublist(0, _peerSettings.maxFrameSize); 200 | fragment = fragment.sublist(_peerSettings.maxFrameSize); 201 | _writeContinuationFrame(streamId, chunk, false); 202 | } 203 | _writeContinuationFrame(streamId, chunk, true); 204 | } 205 | } 206 | 207 | void _writePushPromiseFrameNoFragmentation( 208 | int streamId, int promisedStreamId, List fragment, bool endHeaders) { 209 | var type = FrameType.PUSH_PROMISE; 210 | var flags = endHeaders ? HeadersFrame.FLAG_END_HEADERS : 0; 211 | 212 | var buffer = Uint8List(FRAME_HEADER_SIZE + 4 + fragment.length); 213 | var offset = 0; 214 | 215 | _setFrameHeader(buffer, offset, type, flags, streamId, 4 + fragment.length); 216 | offset += FRAME_HEADER_SIZE; 217 | 218 | setInt32(buffer, offset, promisedStreamId); 219 | buffer.setRange(offset + 4, offset + 4 + fragment.length, fragment); 220 | 221 | _writeData(buffer); 222 | } 223 | 224 | void writePingFrame(int opaqueData, {bool ack = false}) { 225 | var type = FrameType.PING; 226 | var flags = ack ? PingFrame.FLAG_ACK : 0; 227 | 228 | var buffer = Uint8List(FRAME_HEADER_SIZE + PingFrame.FIXED_FRAME_LENGTH); 229 | var offset = 0; 230 | 231 | _setFrameHeader(buffer, 0, type, flags, 0, 8); 232 | offset += FRAME_HEADER_SIZE; 233 | 234 | setInt64(buffer, offset, opaqueData); 235 | _writeData(buffer); 236 | } 237 | 238 | void writeGoawayFrame(int lastStreamId, int errorCode, List debugData) { 239 | var type = FrameType.GOAWAY; 240 | var flags = 0; 241 | 242 | var buffer = Uint8List(FRAME_HEADER_SIZE + 8 + debugData.length); 243 | var offset = 0; 244 | 245 | _setFrameHeader(buffer, offset, type, flags, 0, 8 + debugData.length); 246 | offset += FRAME_HEADER_SIZE; 247 | 248 | setInt32(buffer, offset, lastStreamId); 249 | setInt32(buffer, offset + 4, errorCode); 250 | buffer.setRange(offset + 8, buffer.length, debugData); 251 | 252 | _writeData(buffer); 253 | } 254 | 255 | void writeWindowUpdate(int sizeIncrement, {int streamId = 0}) { 256 | var type = FrameType.WINDOW_UPDATE; 257 | var flags = 0; 258 | 259 | var buffer = 260 | Uint8List(FRAME_HEADER_SIZE + WindowUpdateFrame.FIXED_FRAME_LENGTH); 261 | var offset = 0; 262 | 263 | _setFrameHeader(buffer, offset, type, flags, streamId, 4); 264 | offset += FRAME_HEADER_SIZE; 265 | 266 | setInt32(buffer, offset, sizeIncrement); 267 | 268 | _writeData(buffer); 269 | } 270 | 271 | void _writeData(List bytes) { 272 | _outWriter.add(bytes); 273 | } 274 | 275 | /// Closes the underlying sink and returns [doneFuture]. 276 | Future close() { 277 | return _outWriter.close().whenComplete(() => doneFuture); 278 | } 279 | 280 | /// The future which will complete once this writer is done. 281 | Future get doneFuture => _outWriter.doneFuture; 282 | 283 | void _setFrameHeader(List bytes, int offset, int type, int flags, 284 | int streamId, int length) { 285 | setInt24(bytes, offset, length); 286 | bytes[3] = type; 287 | bytes[4] = flags; 288 | setInt32(bytes, 5, streamId); 289 | 290 | _highestWrittenStreamId = max(_highestWrittenStreamId, streamId); 291 | } 292 | } 293 | -------------------------------------------------------------------------------- /lib/src/frames/frames.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | library http2.src.frames; 6 | 7 | import 'dart:async'; 8 | import 'dart:math' show max; 9 | import 'dart:typed_data'; 10 | 11 | import '../async_utils/async_utils.dart'; 12 | import '../byte_utils.dart'; 13 | import '../hpack/hpack.dart'; 14 | import '../settings/settings.dart'; 15 | import '../sync_errors.dart'; 16 | 17 | part 'frame_types.dart'; 18 | part 'frame_utils.dart'; 19 | part 'frame_reader.dart'; 20 | part 'frame_writer.dart'; 21 | -------------------------------------------------------------------------------- /lib/src/hpack/huffman.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:typed_data'; 6 | 7 | import 'huffman_table.dart'; 8 | 9 | class HuffmanDecodingException implements Exception { 10 | final String _message; 11 | 12 | HuffmanDecodingException(this._message); 13 | 14 | @override 15 | String toString() => 'HuffmanDecodingException: $_message'; 16 | } 17 | 18 | /// A codec used for encoding/decoding using a huffman codec. 19 | class HuffmanCodec { 20 | final HuffmanEncoder _encoder; 21 | final HuffmanDecoder _decoder; 22 | 23 | HuffmanCodec(this._encoder, this._decoder); 24 | 25 | List decode(List bytes) => _decoder.decode(bytes); 26 | 27 | List encode(List bytes) => _encoder.encode(bytes); 28 | } 29 | 30 | /// A huffman decoder based on a [HuffmanTreeNode]. 31 | class HuffmanDecoder { 32 | final HuffmanTreeNode _root; 33 | 34 | HuffmanDecoder(this._root); 35 | 36 | /// Decodes [bytes] using a huffman tree. 37 | List decode(List bytes) { 38 | var buffer = BytesBuilder(); 39 | 40 | var currentByteOffset = 0; 41 | var node = _root; 42 | var currentDepth = 0; 43 | while (currentByteOffset < bytes.length) { 44 | var byte = bytes[currentByteOffset]; 45 | for (var currentBit = 7; currentBit >= 0; currentBit--) { 46 | var right = (byte >> currentBit) & 1 == 1; 47 | if (right) { 48 | node = node.right!; 49 | } else { 50 | node = node.left!; 51 | } 52 | currentDepth++; 53 | if (node.value != null) { 54 | if (node.value == EOS_BYTE) { 55 | throw HuffmanDecodingException( 56 | 'More than 7 bit padding is not allowed. Found entire EOS ' 57 | 'encoding'); 58 | } 59 | buffer.addByte(node.value!); 60 | node = _root; 61 | currentDepth = 0; 62 | } 63 | } 64 | currentByteOffset++; 65 | } 66 | 67 | if (node != _root) { 68 | if (currentDepth > 7) { 69 | throw HuffmanDecodingException( 70 | 'Incomplete encoding of a byte or more than 7 bit padding.'); 71 | } 72 | 73 | while (node.right != null) { 74 | node = node.right!; 75 | } 76 | 77 | if (node.value != 256) { 78 | throw HuffmanDecodingException('Incomplete encoding of a byte.'); 79 | } 80 | } 81 | 82 | return buffer.takeBytes(); 83 | } 84 | } 85 | 86 | /// A huffman encoder based on a list of codewords. 87 | class HuffmanEncoder { 88 | final List _codewords; 89 | 90 | HuffmanEncoder(this._codewords); 91 | 92 | /// Encodes [bytes] using a list of codewords. 93 | List encode(List bytes) { 94 | var buffer = BytesBuilder(); 95 | 96 | var currentByte = 0; 97 | var currentBitOffset = 7; 98 | 99 | void writeValue(int value, int numBits) { 100 | var i = numBits - 1; 101 | while (i >= 0) { 102 | if (currentBitOffset == 7 && i >= 7) { 103 | assert(currentByte == 0); 104 | 105 | buffer.addByte((value >> (i - 7)) & 0xff); 106 | currentBitOffset = 7; 107 | currentByte = 0; 108 | i -= 8; 109 | } else { 110 | currentByte |= ((value >> i) & 1) << currentBitOffset; 111 | 112 | currentBitOffset--; 113 | if (currentBitOffset == -1) { 114 | buffer.addByte(currentByte); 115 | currentBitOffset = 7; 116 | currentByte = 0; 117 | } 118 | i--; 119 | } 120 | } 121 | } 122 | 123 | for (var i = 0; i < bytes.length; i++) { 124 | var byte = bytes[i]; 125 | var value = _codewords[byte]; 126 | writeValue(value.encodedBytes, value.numBits); 127 | } 128 | 129 | if (currentBitOffset < 7) { 130 | writeValue(0xff, 1 + currentBitOffset); 131 | } 132 | 133 | return buffer.takeBytes(); 134 | } 135 | } 136 | 137 | /// Specifies the encoding of a specific value using huffman encoding. 138 | class EncodedHuffmanValue { 139 | /// An integer representation of the encoded bit-string. 140 | final int encodedBytes; 141 | 142 | /// The number of bits in [encodedBytes]. 143 | final int numBits; 144 | 145 | const EncodedHuffmanValue(this.encodedBytes, this.numBits); 146 | } 147 | 148 | /// A node in the huffman tree. 149 | class HuffmanTreeNode { 150 | HuffmanTreeNode? left; 151 | HuffmanTreeNode? right; 152 | int? value; 153 | } 154 | 155 | /// Generates a huffman decoding tree. 156 | HuffmanTreeNode generateHuffmanTree(List valueEncodings) { 157 | var root = HuffmanTreeNode(); 158 | 159 | for (var byteOffset = 0; byteOffset < valueEncodings.length; byteOffset++) { 160 | var entry = valueEncodings[byteOffset]; 161 | 162 | var current = root; 163 | for (var bitNr = 0; bitNr < entry.numBits; bitNr++) { 164 | var right = 165 | ((entry.encodedBytes >> (entry.numBits - bitNr - 1)) & 1) == 1; 166 | 167 | if (right) { 168 | current.right ??= HuffmanTreeNode(); 169 | current = current.right!; 170 | } else { 171 | current.left ??= HuffmanTreeNode(); 172 | current = current.left!; 173 | } 174 | } 175 | 176 | current.value = byteOffset; 177 | } 178 | 179 | return root; 180 | } 181 | -------------------------------------------------------------------------------- /lib/src/hpack/huffman_table.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'huffman.dart'; 6 | 7 | /// The huffman codec for encoding/decoding HTTP/2 header blocks. 8 | final HuffmanCodec http2HuffmanCodec = HuffmanCodec(HuffmanEncoder(_codeWords), 9 | HuffmanDecoder(generateHuffmanTree(_codeWords))); 10 | 11 | /// This is the integer representing the End-of-String symbol 12 | /// (it is not representable by a byte). 13 | const int EOS_BYTE = 256; 14 | 15 | /// This list of byte encodings via huffman encoding was generated from the 16 | /// HPACK specification. 17 | const List _codeWords = [ 18 | EncodedHuffmanValue(0x1ff8, 13), 19 | EncodedHuffmanValue(0x7fffd8, 23), 20 | EncodedHuffmanValue(0xfffffe2, 28), 21 | EncodedHuffmanValue(0xfffffe3, 28), 22 | EncodedHuffmanValue(0xfffffe4, 28), 23 | EncodedHuffmanValue(0xfffffe5, 28), 24 | EncodedHuffmanValue(0xfffffe6, 28), 25 | EncodedHuffmanValue(0xfffffe7, 28), 26 | EncodedHuffmanValue(0xfffffe8, 28), 27 | EncodedHuffmanValue(0xffffea, 24), 28 | EncodedHuffmanValue(0x3ffffffc, 30), 29 | EncodedHuffmanValue(0xfffffe9, 28), 30 | EncodedHuffmanValue(0xfffffea, 28), 31 | EncodedHuffmanValue(0x3ffffffd, 30), 32 | EncodedHuffmanValue(0xfffffeb, 28), 33 | EncodedHuffmanValue(0xfffffec, 28), 34 | EncodedHuffmanValue(0xfffffed, 28), 35 | EncodedHuffmanValue(0xfffffee, 28), 36 | EncodedHuffmanValue(0xfffffef, 28), 37 | EncodedHuffmanValue(0xffffff0, 28), 38 | EncodedHuffmanValue(0xffffff1, 28), 39 | EncodedHuffmanValue(0xffffff2, 28), 40 | EncodedHuffmanValue(0x3ffffffe, 30), 41 | EncodedHuffmanValue(0xffffff3, 28), 42 | EncodedHuffmanValue(0xffffff4, 28), 43 | EncodedHuffmanValue(0xffffff5, 28), 44 | EncodedHuffmanValue(0xffffff6, 28), 45 | EncodedHuffmanValue(0xffffff7, 28), 46 | EncodedHuffmanValue(0xffffff8, 28), 47 | EncodedHuffmanValue(0xffffff9, 28), 48 | EncodedHuffmanValue(0xffffffa, 28), 49 | EncodedHuffmanValue(0xffffffb, 28), 50 | EncodedHuffmanValue(0x14, 6), 51 | EncodedHuffmanValue(0x3f8, 10), 52 | EncodedHuffmanValue(0x3f9, 10), 53 | EncodedHuffmanValue(0xffa, 12), 54 | EncodedHuffmanValue(0x1ff9, 13), 55 | EncodedHuffmanValue(0x15, 6), 56 | EncodedHuffmanValue(0xf8, 8), 57 | EncodedHuffmanValue(0x7fa, 11), 58 | EncodedHuffmanValue(0x3fa, 10), 59 | EncodedHuffmanValue(0x3fb, 10), 60 | EncodedHuffmanValue(0xf9, 8), 61 | EncodedHuffmanValue(0x7fb, 11), 62 | EncodedHuffmanValue(0xfa, 8), 63 | EncodedHuffmanValue(0x16, 6), 64 | EncodedHuffmanValue(0x17, 6), 65 | EncodedHuffmanValue(0x18, 6), 66 | EncodedHuffmanValue(0x0, 5), 67 | EncodedHuffmanValue(0x1, 5), 68 | EncodedHuffmanValue(0x2, 5), 69 | EncodedHuffmanValue(0x19, 6), 70 | EncodedHuffmanValue(0x1a, 6), 71 | EncodedHuffmanValue(0x1b, 6), 72 | EncodedHuffmanValue(0x1c, 6), 73 | EncodedHuffmanValue(0x1d, 6), 74 | EncodedHuffmanValue(0x1e, 6), 75 | EncodedHuffmanValue(0x1f, 6), 76 | EncodedHuffmanValue(0x5c, 7), 77 | EncodedHuffmanValue(0xfb, 8), 78 | EncodedHuffmanValue(0x7ffc, 15), 79 | EncodedHuffmanValue(0x20, 6), 80 | EncodedHuffmanValue(0xffb, 12), 81 | EncodedHuffmanValue(0x3fc, 10), 82 | EncodedHuffmanValue(0x1ffa, 13), 83 | EncodedHuffmanValue(0x21, 6), 84 | EncodedHuffmanValue(0x5d, 7), 85 | EncodedHuffmanValue(0x5e, 7), 86 | EncodedHuffmanValue(0x5f, 7), 87 | EncodedHuffmanValue(0x60, 7), 88 | EncodedHuffmanValue(0x61, 7), 89 | EncodedHuffmanValue(0x62, 7), 90 | EncodedHuffmanValue(0x63, 7), 91 | EncodedHuffmanValue(0x64, 7), 92 | EncodedHuffmanValue(0x65, 7), 93 | EncodedHuffmanValue(0x66, 7), 94 | EncodedHuffmanValue(0x67, 7), 95 | EncodedHuffmanValue(0x68, 7), 96 | EncodedHuffmanValue(0x69, 7), 97 | EncodedHuffmanValue(0x6a, 7), 98 | EncodedHuffmanValue(0x6b, 7), 99 | EncodedHuffmanValue(0x6c, 7), 100 | EncodedHuffmanValue(0x6d, 7), 101 | EncodedHuffmanValue(0x6e, 7), 102 | EncodedHuffmanValue(0x6f, 7), 103 | EncodedHuffmanValue(0x70, 7), 104 | EncodedHuffmanValue(0x71, 7), 105 | EncodedHuffmanValue(0x72, 7), 106 | EncodedHuffmanValue(0xfc, 8), 107 | EncodedHuffmanValue(0x73, 7), 108 | EncodedHuffmanValue(0xfd, 8), 109 | EncodedHuffmanValue(0x1ffb, 13), 110 | EncodedHuffmanValue(0x7fff0, 19), 111 | EncodedHuffmanValue(0x1ffc, 13), 112 | EncodedHuffmanValue(0x3ffc, 14), 113 | EncodedHuffmanValue(0x22, 6), 114 | EncodedHuffmanValue(0x7ffd, 15), 115 | EncodedHuffmanValue(0x3, 5), 116 | EncodedHuffmanValue(0x23, 6), 117 | EncodedHuffmanValue(0x4, 5), 118 | EncodedHuffmanValue(0x24, 6), 119 | EncodedHuffmanValue(0x5, 5), 120 | EncodedHuffmanValue(0x25, 6), 121 | EncodedHuffmanValue(0x26, 6), 122 | EncodedHuffmanValue(0x27, 6), 123 | EncodedHuffmanValue(0x6, 5), 124 | EncodedHuffmanValue(0x74, 7), 125 | EncodedHuffmanValue(0x75, 7), 126 | EncodedHuffmanValue(0x28, 6), 127 | EncodedHuffmanValue(0x29, 6), 128 | EncodedHuffmanValue(0x2a, 6), 129 | EncodedHuffmanValue(0x7, 5), 130 | EncodedHuffmanValue(0x2b, 6), 131 | EncodedHuffmanValue(0x76, 7), 132 | EncodedHuffmanValue(0x2c, 6), 133 | EncodedHuffmanValue(0x8, 5), 134 | EncodedHuffmanValue(0x9, 5), 135 | EncodedHuffmanValue(0x2d, 6), 136 | EncodedHuffmanValue(0x77, 7), 137 | EncodedHuffmanValue(0x78, 7), 138 | EncodedHuffmanValue(0x79, 7), 139 | EncodedHuffmanValue(0x7a, 7), 140 | EncodedHuffmanValue(0x7b, 7), 141 | EncodedHuffmanValue(0x7ffe, 15), 142 | EncodedHuffmanValue(0x7fc, 11), 143 | EncodedHuffmanValue(0x3ffd, 14), 144 | EncodedHuffmanValue(0x1ffd, 13), 145 | EncodedHuffmanValue(0xffffffc, 28), 146 | EncodedHuffmanValue(0xfffe6, 20), 147 | EncodedHuffmanValue(0x3fffd2, 22), 148 | EncodedHuffmanValue(0xfffe7, 20), 149 | EncodedHuffmanValue(0xfffe8, 20), 150 | EncodedHuffmanValue(0x3fffd3, 22), 151 | EncodedHuffmanValue(0x3fffd4, 22), 152 | EncodedHuffmanValue(0x3fffd5, 22), 153 | EncodedHuffmanValue(0x7fffd9, 23), 154 | EncodedHuffmanValue(0x3fffd6, 22), 155 | EncodedHuffmanValue(0x7fffda, 23), 156 | EncodedHuffmanValue(0x7fffdb, 23), 157 | EncodedHuffmanValue(0x7fffdc, 23), 158 | EncodedHuffmanValue(0x7fffdd, 23), 159 | EncodedHuffmanValue(0x7fffde, 23), 160 | EncodedHuffmanValue(0xffffeb, 24), 161 | EncodedHuffmanValue(0x7fffdf, 23), 162 | EncodedHuffmanValue(0xffffec, 24), 163 | EncodedHuffmanValue(0xffffed, 24), 164 | EncodedHuffmanValue(0x3fffd7, 22), 165 | EncodedHuffmanValue(0x7fffe0, 23), 166 | EncodedHuffmanValue(0xffffee, 24), 167 | EncodedHuffmanValue(0x7fffe1, 23), 168 | EncodedHuffmanValue(0x7fffe2, 23), 169 | EncodedHuffmanValue(0x7fffe3, 23), 170 | EncodedHuffmanValue(0x7fffe4, 23), 171 | EncodedHuffmanValue(0x1fffdc, 21), 172 | EncodedHuffmanValue(0x3fffd8, 22), 173 | EncodedHuffmanValue(0x7fffe5, 23), 174 | EncodedHuffmanValue(0x3fffd9, 22), 175 | EncodedHuffmanValue(0x7fffe6, 23), 176 | EncodedHuffmanValue(0x7fffe7, 23), 177 | EncodedHuffmanValue(0xffffef, 24), 178 | EncodedHuffmanValue(0x3fffda, 22), 179 | EncodedHuffmanValue(0x1fffdd, 21), 180 | EncodedHuffmanValue(0xfffe9, 20), 181 | EncodedHuffmanValue(0x3fffdb, 22), 182 | EncodedHuffmanValue(0x3fffdc, 22), 183 | EncodedHuffmanValue(0x7fffe8, 23), 184 | EncodedHuffmanValue(0x7fffe9, 23), 185 | EncodedHuffmanValue(0x1fffde, 21), 186 | EncodedHuffmanValue(0x7fffea, 23), 187 | EncodedHuffmanValue(0x3fffdd, 22), 188 | EncodedHuffmanValue(0x3fffde, 22), 189 | EncodedHuffmanValue(0xfffff0, 24), 190 | EncodedHuffmanValue(0x1fffdf, 21), 191 | EncodedHuffmanValue(0x3fffdf, 22), 192 | EncodedHuffmanValue(0x7fffeb, 23), 193 | EncodedHuffmanValue(0x7fffec, 23), 194 | EncodedHuffmanValue(0x1fffe0, 21), 195 | EncodedHuffmanValue(0x1fffe1, 21), 196 | EncodedHuffmanValue(0x3fffe0, 22), 197 | EncodedHuffmanValue(0x1fffe2, 21), 198 | EncodedHuffmanValue(0x7fffed, 23), 199 | EncodedHuffmanValue(0x3fffe1, 22), 200 | EncodedHuffmanValue(0x7fffee, 23), 201 | EncodedHuffmanValue(0x7fffef, 23), 202 | EncodedHuffmanValue(0xfffea, 20), 203 | EncodedHuffmanValue(0x3fffe2, 22), 204 | EncodedHuffmanValue(0x3fffe3, 22), 205 | EncodedHuffmanValue(0x3fffe4, 22), 206 | EncodedHuffmanValue(0x7ffff0, 23), 207 | EncodedHuffmanValue(0x3fffe5, 22), 208 | EncodedHuffmanValue(0x3fffe6, 22), 209 | EncodedHuffmanValue(0x7ffff1, 23), 210 | EncodedHuffmanValue(0x3ffffe0, 26), 211 | EncodedHuffmanValue(0x3ffffe1, 26), 212 | EncodedHuffmanValue(0xfffeb, 20), 213 | EncodedHuffmanValue(0x7fff1, 19), 214 | EncodedHuffmanValue(0x3fffe7, 22), 215 | EncodedHuffmanValue(0x7ffff2, 23), 216 | EncodedHuffmanValue(0x3fffe8, 22), 217 | EncodedHuffmanValue(0x1ffffec, 25), 218 | EncodedHuffmanValue(0x3ffffe2, 26), 219 | EncodedHuffmanValue(0x3ffffe3, 26), 220 | EncodedHuffmanValue(0x3ffffe4, 26), 221 | EncodedHuffmanValue(0x7ffffde, 27), 222 | EncodedHuffmanValue(0x7ffffdf, 27), 223 | EncodedHuffmanValue(0x3ffffe5, 26), 224 | EncodedHuffmanValue(0xfffff1, 24), 225 | EncodedHuffmanValue(0x1ffffed, 25), 226 | EncodedHuffmanValue(0x7fff2, 19), 227 | EncodedHuffmanValue(0x1fffe3, 21), 228 | EncodedHuffmanValue(0x3ffffe6, 26), 229 | EncodedHuffmanValue(0x7ffffe0, 27), 230 | EncodedHuffmanValue(0x7ffffe1, 27), 231 | EncodedHuffmanValue(0x3ffffe7, 26), 232 | EncodedHuffmanValue(0x7ffffe2, 27), 233 | EncodedHuffmanValue(0xfffff2, 24), 234 | EncodedHuffmanValue(0x1fffe4, 21), 235 | EncodedHuffmanValue(0x1fffe5, 21), 236 | EncodedHuffmanValue(0x3ffffe8, 26), 237 | EncodedHuffmanValue(0x3ffffe9, 26), 238 | EncodedHuffmanValue(0xffffffd, 28), 239 | EncodedHuffmanValue(0x7ffffe3, 27), 240 | EncodedHuffmanValue(0x7ffffe4, 27), 241 | EncodedHuffmanValue(0x7ffffe5, 27), 242 | EncodedHuffmanValue(0xfffec, 20), 243 | EncodedHuffmanValue(0xfffff3, 24), 244 | EncodedHuffmanValue(0xfffed, 20), 245 | EncodedHuffmanValue(0x1fffe6, 21), 246 | EncodedHuffmanValue(0x3fffe9, 22), 247 | EncodedHuffmanValue(0x1fffe7, 21), 248 | EncodedHuffmanValue(0x1fffe8, 21), 249 | EncodedHuffmanValue(0x7ffff3, 23), 250 | EncodedHuffmanValue(0x3fffea, 22), 251 | EncodedHuffmanValue(0x3fffeb, 22), 252 | EncodedHuffmanValue(0x1ffffee, 25), 253 | EncodedHuffmanValue(0x1ffffef, 25), 254 | EncodedHuffmanValue(0xfffff4, 24), 255 | EncodedHuffmanValue(0xfffff5, 24), 256 | EncodedHuffmanValue(0x3ffffea, 26), 257 | EncodedHuffmanValue(0x7ffff4, 23), 258 | EncodedHuffmanValue(0x3ffffeb, 26), 259 | EncodedHuffmanValue(0x7ffffe6, 27), 260 | EncodedHuffmanValue(0x3ffffec, 26), 261 | EncodedHuffmanValue(0x3ffffed, 26), 262 | EncodedHuffmanValue(0x7ffffe7, 27), 263 | EncodedHuffmanValue(0x7ffffe8, 27), 264 | EncodedHuffmanValue(0x7ffffe9, 27), 265 | EncodedHuffmanValue(0x7ffffea, 27), 266 | EncodedHuffmanValue(0x7ffffeb, 27), 267 | EncodedHuffmanValue(0xffffffe, 28), 268 | EncodedHuffmanValue(0x7ffffec, 27), 269 | EncodedHuffmanValue(0x7ffffed, 27), 270 | EncodedHuffmanValue(0x7ffffee, 27), 271 | EncodedHuffmanValue(0x7ffffef, 27), 272 | EncodedHuffmanValue(0x7fffff0, 27), 273 | EncodedHuffmanValue(0x3ffffee, 26), 274 | EncodedHuffmanValue(0x3fffffff, 30), 275 | ]; 276 | -------------------------------------------------------------------------------- /lib/src/ping/ping_handler.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import '../error_handler.dart'; 8 | import '../frames/frames.dart'; 9 | import '../sync_errors.dart'; 10 | 11 | /// Responsible for pinging the other end and for handling pings from the 12 | /// other end. 13 | // TODO: We currently write unconditionally to the [FrameWriter]: we might want 14 | // to consider be more aware what [Framewriter.bufferIndicator.wouldBuffer] 15 | // says. 16 | class PingHandler extends Object with TerminatableMixin { 17 | final FrameWriter _frameWriter; 18 | final Map _remainingPings = {}; 19 | final Sink? pingReceived; 20 | final bool Function() isListeningToPings; 21 | int _nextId = 1; 22 | 23 | PingHandler(this._frameWriter, StreamController pingStream) 24 | : pingReceived = pingStream.sink, 25 | isListeningToPings = (() => pingStream.hasListener); 26 | 27 | @override 28 | void onTerminated(Object? error) { 29 | final remainingPings = _remainingPings.values.toList(); 30 | _remainingPings.clear(); 31 | for (final ping in remainingPings) { 32 | ping.completeError( 33 | error ?? 'Remaining ping completed with unspecified error'); 34 | } 35 | } 36 | 37 | void processPingFrame(PingFrame frame) { 38 | ensureNotTerminatedSync(() { 39 | if (frame.header.streamId != 0) { 40 | throw ProtocolException('Ping frames must have a stream id of 0.'); 41 | } 42 | 43 | if (!frame.hasAckFlag) { 44 | if (isListeningToPings()) { 45 | pingReceived?.add(frame.opaqueData); 46 | } 47 | _frameWriter.writePingFrame(frame.opaqueData, ack: true); 48 | } else { 49 | var c = _remainingPings.remove(frame.opaqueData); 50 | if (c != null) { 51 | c.complete(); 52 | } else { 53 | // NOTE: It is not specified what happens when one gets an ACK for a 54 | // ping we never sent. We be very strict and fail in this case. 55 | throw ProtocolException( 56 | 'Received ping ack with unknown opaque data.'); 57 | } 58 | } 59 | }); 60 | } 61 | 62 | Future ping() { 63 | return ensureNotTerminatedAsync(() { 64 | var c = Completer(); 65 | var id = _nextId++; 66 | _remainingPings[id] = c; 67 | _frameWriter.writePingFrame(id); 68 | return c.future; 69 | }); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/src/settings/settings.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import '../error_handler.dart'; 8 | import '../frames/frames.dart'; 9 | import '../hpack/hpack.dart'; 10 | import '../sync_errors.dart'; 11 | 12 | /// The settings a remote peer can choose to set. 13 | class ActiveSettings { 14 | /// Allows the sender to inform the remote endpoint of the maximum size of the 15 | /// header compression table used to decode header blocks, in octets. The 16 | /// encoder can select any size equal to or less than this value by using 17 | /// signaling specific to the header compression format inside a header block. 18 | /// The initial value is 4,096 octets. 19 | int headerTableSize; 20 | 21 | /// This setting can be use to disable server push (Section 8.2). An endpoint 22 | /// MUST NOT send a PUSH_PROMISE frame if it receives this parameter set to a 23 | /// value of 0. An endpoint that has both set this parameter to 0 and had it 24 | /// acknowledged MUST treat the receipt of a PUSH_PROMISE frame as a 25 | /// connection error (Section 5.4.1) of type PROTOCOL_ERROR. 26 | /// 27 | /// The initial value is 1, which indicates that server push is permitted. 28 | /// Any value other than 0 or 1 MUST be treated as a connection error 29 | /// (Section 5.4.1) of type PROTOCOL_ERROR. 30 | bool enablePush; 31 | 32 | /// Indicates the maximum number of concurrent streams that the sender will 33 | /// allow. This limit is directional: it applies to the number of streams that 34 | /// the sender permits the receiver to create. Initially there is no limit to 35 | /// this value. It is recommended that this value be no smaller than 100, so 36 | /// as to not unnecessarily limit parallelism. 37 | /// 38 | /// A value of 0 for SETTINGS_MAX_CONCURRENT_STREAMS SHOULD NOT be treated as 39 | /// special by endpoints. A zero value does prevent the creation of new 40 | /// streams, however this can also happen for any limit that is exhausted with 41 | /// active streams. Servers SHOULD only set a zero value for short durations; 42 | /// if a server does not wish to accept requests, closing the connection is 43 | /// more appropriate. 44 | int? maxConcurrentStreams; 45 | 46 | /// Indicates the sender's initial window size (in octets) for stream level 47 | /// flow control. The initial value is 2^16-1 (65,535) octets. 48 | /// 49 | /// This setting affects the window size of all streams, including existing 50 | /// streams, see Section 6.9.2. 51 | /// Values above the maximum flow control window size of 231-1 MUST be treated 52 | /// as a connection error (Section 5.4.1) of type FLOW_CONTROL_ERROR. 53 | int initialWindowSize; 54 | 55 | /// Indicates the size of the largest frame payload that the sender is willing 56 | /// to receive, in octets. 57 | /// 58 | /// The initial value is 2^14 (16,384) octets. The value advertised by an 59 | /// endpoint MUST be between this initial value and the maximum allowed frame 60 | /// size (2^24-1 or 16,777,215 octets), inclusive. Values outside this range 61 | /// MUST be treated as a connection error (Section 5.4.1) of type 62 | /// PROTOCOL_ERROR. 63 | int maxFrameSize; 64 | 65 | /// This advisory setting informs a peer of the maximum size of header list 66 | /// that the sender is prepared to accept, in octets. The value is based on 67 | /// the uncompressed size of header fields, including the length of the name 68 | /// and value in octets plus an overhead of 32 octets for each header field. 69 | /// 70 | /// For any given request, a lower limit than what is advertised MAY be 71 | /// enforced. The initial value of this setting is unlimited. 72 | int? maxHeaderListSize; 73 | 74 | ActiveSettings( 75 | {this.headerTableSize = 4096, 76 | this.enablePush = true, 77 | this.maxConcurrentStreams, 78 | this.initialWindowSize = (1 << 16) - 1, 79 | this.maxFrameSize = 1 << 14, 80 | this.maxHeaderListSize}); 81 | } 82 | 83 | /// Handles remote and local connection [Setting]s. 84 | /// 85 | /// Incoming [SettingsFrame]s will be handled here to update the peer settings. 86 | /// Changes to [_toBeAcknowledgedSettings] can be made, the peer will then be 87 | /// notified of the setting changes it should use. 88 | class SettingsHandler extends Object with TerminatableMixin { 89 | /// Certain settings changes can change the maximum allowed dynamic table 90 | /// size used by the HPack encoder. 91 | final HPackEncoder _hpackEncoder; 92 | 93 | final FrameWriter _frameWriter; 94 | 95 | /// A list of outstanding setting changes. 96 | final List> _toBeAcknowledgedSettings = []; 97 | 98 | /// A list of completers for outstanding setting changes. 99 | final List _toBeAcknowledgedCompleters = []; 100 | 101 | /// The local settings, which the remote side ACKed to obey. 102 | final ActiveSettings _acknowledgedSettings; 103 | 104 | /// The peer settings, which we ACKed and are obeying. 105 | final ActiveSettings _peerSettings; 106 | 107 | final _onInitialWindowSizeChangeController = 108 | StreamController.broadcast(sync: true); 109 | 110 | /// Events are fired when a SettingsFrame changes the initial size 111 | /// of stream windows. 112 | Stream get onInitialWindowSizeChange => 113 | _onInitialWindowSizeChangeController.stream; 114 | 115 | SettingsHandler(this._hpackEncoder, this._frameWriter, 116 | this._acknowledgedSettings, this._peerSettings); 117 | 118 | /// The settings for this endpoint of the connection which the remote peer 119 | /// has ACKed and uses. 120 | ActiveSettings get acknowledgedSettings => _acknowledgedSettings; 121 | 122 | /// The settings for the remote endpoint of the connection which this 123 | /// endpoint should use. 124 | ActiveSettings get peerSettings => _peerSettings; 125 | 126 | /// Handles an incoming [SettingsFrame] which can be an ACK or a settings 127 | /// change. 128 | void handleSettingsFrame(SettingsFrame frame) { 129 | ensureNotTerminatedSync(() { 130 | assert(frame.header.streamId == 0); 131 | 132 | if (frame.hasAckFlag) { 133 | assert(frame.header.length == 0); 134 | 135 | if (_toBeAcknowledgedSettings.isEmpty) { 136 | // NOTE: The specification does not say anything about ACKed settings 137 | // which were never sent to the other side. We consider this definitly 138 | // an error. 139 | throw ProtocolException( 140 | 'Received an acknowledged settings frame which did not have a ' 141 | 'outstanding settings request.'); 142 | } 143 | var settingChanges = _toBeAcknowledgedSettings.removeAt(0); 144 | var completer = _toBeAcknowledgedCompleters.removeAt(0); 145 | _modifySettings(_acknowledgedSettings, settingChanges, false); 146 | completer.complete(); 147 | } else { 148 | _modifySettings(_peerSettings, frame.settings, true); 149 | _frameWriter.writeSettingsAckFrame(); 150 | } 151 | }); 152 | } 153 | 154 | @override 155 | void onTerminated(Object? error) { 156 | _toBeAcknowledgedSettings.clear(); 157 | for (var completer in _toBeAcknowledgedCompleters) { 158 | completer.completeError(error!); 159 | } 160 | } 161 | 162 | Future changeSettings(List changes) { 163 | return ensureNotTerminatedAsync(() { 164 | // TODO: Have a timeout: When ACK doesn't get back in a reasonable time 165 | // frame we should quit with ErrorCode.SETTINGS_TIMEOUT. 166 | var completer = Completer(); 167 | _toBeAcknowledgedSettings.add(changes); 168 | _toBeAcknowledgedCompleters.add(completer); 169 | _frameWriter.writeSettingsFrame(changes); 170 | return completer.future; 171 | }); 172 | } 173 | 174 | void _modifySettings( 175 | ActiveSettings base, List changes, bool peerSettings) { 176 | for (var setting in changes) { 177 | switch (setting.identifier) { 178 | case Setting.SETTINGS_ENABLE_PUSH: 179 | if (setting.value == 0) { 180 | base.enablePush = false; 181 | } else if (setting.value == 1) { 182 | base.enablePush = true; 183 | } else { 184 | throw ProtocolException( 185 | 'The push setting can be only set to 0 or 1.'); 186 | } 187 | break; 188 | 189 | case Setting.SETTINGS_HEADER_TABLE_SIZE: 190 | base.headerTableSize = setting.value; 191 | if (peerSettings) { 192 | _hpackEncoder.updateMaxSendingHeaderTableSize(base.headerTableSize); 193 | } 194 | break; 195 | 196 | case Setting.SETTINGS_MAX_HEADER_LIST_SIZE: 197 | // TODO: Propagate this signal to the HPackContext. 198 | base.maxHeaderListSize = setting.value; 199 | break; 200 | 201 | case Setting.SETTINGS_MAX_CONCURRENT_STREAMS: 202 | // NOTE: We will not force closing of existing streams if the limit is 203 | // lower than the current number of open streams. But we will prevent 204 | // new streams from being created if the number of existing streams 205 | // is above this limit. 206 | base.maxConcurrentStreams = setting.value; 207 | break; 208 | 209 | case Setting.SETTINGS_INITIAL_WINDOW_SIZE: 210 | if (setting.value < (1 << 31)) { 211 | var difference = setting.value - base.initialWindowSize; 212 | _onInitialWindowSizeChangeController.add(difference); 213 | base.initialWindowSize = setting.value; 214 | } else { 215 | throw FlowControlException('Invalid initial window size.'); 216 | } 217 | break; 218 | 219 | default: 220 | // Spec says to ignore unknown settings. 221 | break; 222 | } 223 | } 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /lib/src/sync_errors.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | class ProtocolException implements Exception { 6 | final String _message; 7 | 8 | ProtocolException(this._message); 9 | 10 | @override 11 | String toString() => 'ProtocolError: $_message'; 12 | } 13 | 14 | class FlowControlException implements Exception { 15 | final String _message; 16 | 17 | FlowControlException(this._message); 18 | 19 | @override 20 | String toString() => 'FlowControlException: $_message'; 21 | } 22 | 23 | class FrameSizeException implements Exception { 24 | final String _message; 25 | 26 | FrameSizeException(this._message); 27 | 28 | @override 29 | String toString() => 'FrameSizeException: $_message'; 30 | } 31 | 32 | class TerminatedException implements Exception { 33 | @override 34 | String toString() => 'TerminatedException: The object has been terminated.'; 35 | } 36 | 37 | class StreamException implements Exception { 38 | final String _message; 39 | final int streamId; 40 | 41 | StreamException(this.streamId, this._message); 42 | 43 | @override 44 | String toString() => 'StreamException(stream id: $streamId): $_message'; 45 | } 46 | 47 | class StreamClosedException extends StreamException { 48 | StreamClosedException(super.streamId, [super.message = '']); 49 | 50 | @override 51 | String toString() => 'StreamClosedException(stream id: $streamId): $_message'; 52 | } 53 | -------------------------------------------------------------------------------- /lib/transport.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:io'; 7 | 8 | import 'src/connection.dart'; 9 | import 'src/hpack/hpack.dart' show Header; 10 | 11 | export 'src/frames/frames.dart' show ErrorCode; 12 | export 'src/hpack/hpack.dart' show Header; 13 | 14 | typedef ActiveStateHandler = void Function(bool isActive); 15 | 16 | /// Settings for a [TransportConnection]. 17 | abstract class Settings { 18 | /// The maximum number of concurrent streams the remote end can open 19 | /// (defaults to being unlimited). 20 | final int? concurrentStreamLimit; 21 | 22 | /// The default stream window size the remote peer can use when creating new 23 | /// streams (defaults to 65535 bytes). 24 | final int? streamWindowSize; 25 | 26 | const Settings({this.concurrentStreamLimit, this.streamWindowSize}); 27 | } 28 | 29 | /// Settings for a [TransportConnection] a server can make. 30 | class ServerSettings extends Settings { 31 | const ServerSettings({super.concurrentStreamLimit, super.streamWindowSize}); 32 | } 33 | 34 | /// Settings for a [TransportConnection] a client can make. 35 | class ClientSettings extends Settings { 36 | /// Whether the client allows pushes from the server (defaults to false). 37 | final bool allowServerPushes; 38 | 39 | const ClientSettings( 40 | {super.concurrentStreamLimit, 41 | super.streamWindowSize, 42 | this.allowServerPushes = false}); 43 | } 44 | 45 | /// Represents a HTTP/2 connection. 46 | abstract class TransportConnection { 47 | /// Pings the other end. 48 | Future ping(); 49 | 50 | /// Sets the active state callback. 51 | /// 52 | /// This callback is invoked with `true` when the number of active streams 53 | /// goes from 0 to 1 (the connection goes from idle to active), and with 54 | /// `false` when the number of active streams becomes 0 (the connection goes 55 | /// from active to idle). 56 | set onActiveStateChanged(ActiveStateHandler callback); 57 | 58 | /// Future which completes when the first SETTINGS frame is received from 59 | /// the peer. 60 | Future get onInitialPeerSettingsReceived; 61 | 62 | /// Stream which emits an event with the ping id every time a ping is received 63 | /// on this connection. 64 | Stream get onPingReceived; 65 | 66 | /// Stream which emits an event every time a ping is received on this 67 | /// connection. 68 | Stream get onFrameReceived; 69 | 70 | /// Finish this connection. 71 | /// 72 | /// No new streams will be accepted or can be created. 73 | Future finish(); 74 | 75 | /// Terminates this connection forcefully. 76 | Future terminate([int? errorCode]); 77 | } 78 | 79 | abstract class ClientTransportConnection extends TransportConnection { 80 | factory ClientTransportConnection.viaSocket(Socket socket, 81 | {ClientSettings? settings}) => 82 | ClientTransportConnection.viaStreams(socket, socket, settings: settings); 83 | 84 | factory ClientTransportConnection.viaStreams( 85 | Stream> incoming, StreamSink> outgoing, 86 | {ClientSettings? settings}) { 87 | settings ??= const ClientSettings(); 88 | return ClientConnection(incoming, outgoing, settings); 89 | } 90 | 91 | /// Whether this connection is open and can be used to make new requests 92 | /// via [makeRequest]. 93 | bool get isOpen; 94 | 95 | /// Creates a new outgoing stream. 96 | ClientTransportStream makeRequest(List
headers, 97 | {bool endStream = false}); 98 | } 99 | 100 | abstract class ServerTransportConnection extends TransportConnection { 101 | factory ServerTransportConnection.viaSocket(Socket socket, 102 | {ServerSettings? settings}) { 103 | return ServerTransportConnection.viaStreams(socket, socket, 104 | settings: settings); 105 | } 106 | 107 | factory ServerTransportConnection.viaStreams( 108 | Stream> incoming, StreamSink> outgoing, 109 | {ServerSettings? settings = 110 | const ServerSettings(concurrentStreamLimit: 1000)}) { 111 | settings ??= const ServerSettings(); 112 | return ServerConnection(incoming, outgoing, settings); 113 | } 114 | 115 | /// Incoming HTTP/2 streams. 116 | Stream get incomingStreams; 117 | } 118 | 119 | /// Represents a HTTP/2 stream. 120 | abstract class TransportStream { 121 | /// The id of this stream. 122 | /// 123 | /// * odd numbered streams are client streams 124 | /// * even numbered streams are opened from the server 125 | int get id; 126 | 127 | /// A stream of data and/or headers from the remote end. 128 | Stream get incomingMessages; 129 | 130 | /// A sink for writing data and/or headers to the remote end. 131 | StreamSink get outgoingMessages; 132 | 133 | /// Sets the termination handler on this stream. 134 | /// 135 | /// The handler will be called if the stream receives an RST_STREAM frame. 136 | set onTerminated(void Function(int?) value); 137 | 138 | /// Terminates this HTTP/2 stream in an un-normal way. 139 | /// 140 | /// For normal termination, one can cancel the [StreamSubscription] from 141 | /// `incoming.listen()` and close the `outgoing` [StreamSink]. 142 | /// 143 | /// Terminating this HTTP/2 stream will free up all resources associated with 144 | /// it locally and will notify the remote end that this stream is no longer 145 | /// used. 146 | void terminate(); 147 | 148 | // For convenience only. 149 | void sendHeaders(List
headers, {bool endStream = false}) { 150 | outgoingMessages.add(HeadersStreamMessage(headers, endStream: endStream)); 151 | if (endStream) outgoingMessages.close(); 152 | } 153 | 154 | void sendData(List bytes, {bool endStream = false}) { 155 | outgoingMessages.add(DataStreamMessage(bytes, endStream: endStream)); 156 | if (endStream) outgoingMessages.close(); 157 | } 158 | } 159 | 160 | abstract class ClientTransportStream extends TransportStream { 161 | /// Streams which the remote end pushed to this endpoint. 162 | /// 163 | /// If peer pushes were enabled, the client is responsible to either 164 | /// handle or reject any peer push. 165 | Stream get peerPushes; 166 | } 167 | 168 | abstract class ServerTransportStream extends TransportStream { 169 | /// Whether a method to [push] will succeed. Requirements for this getter to 170 | /// return `true` are: 171 | /// * this stream must be in the Open or HalfClosedRemote state 172 | /// * the client needs to have the "enable push" settings enabled 173 | /// * the number of active streams has not reached the maximum 174 | bool get canPush; 175 | 176 | /// Pushes a new stream to the remote peer. 177 | ServerTransportStream push(List
requestHeaders); 178 | } 179 | 180 | /// Represents a message which can be sent over a HTTP/2 stream. 181 | abstract class StreamMessage { 182 | final bool endStream; 183 | 184 | StreamMessage({bool? endStream}) : endStream = endStream ?? false; 185 | } 186 | 187 | /// Represents a data message which can be sent over a HTTP/2 stream. 188 | class DataStreamMessage extends StreamMessage { 189 | final List bytes; 190 | 191 | DataStreamMessage(this.bytes, {super.endStream}); 192 | 193 | @override 194 | String toString() => 'DataStreamMessage(${bytes.length} bytes)'; 195 | } 196 | 197 | /// Represents a headers message which can be sent over a HTTP/2 stream. 198 | class HeadersStreamMessage extends StreamMessage { 199 | final List
headers; 200 | 201 | HeadersStreamMessage(this.headers, {super.endStream}); 202 | 203 | @override 204 | String toString() => 'HeadersStreamMessage(${headers.length} headers)'; 205 | } 206 | 207 | /// Represents a remote stream push. 208 | class TransportStreamPush { 209 | /// The request headers which [stream] is the response to. 210 | final List
requestHeaders; 211 | 212 | /// The remote stream push. 213 | final ClientTransportStream stream; 214 | 215 | TransportStreamPush(this.requestHeaders, this.stream); 216 | 217 | @override 218 | String toString() => 219 | 'TransportStreamPush(${requestHeaders.length} request headers headers)'; 220 | } 221 | 222 | /// An exception thrown by the HTTP/2 implementation. 223 | class TransportException implements Exception { 224 | final String message; 225 | 226 | TransportException(this.message); 227 | 228 | @override 229 | String toString() => 'HTTP/2 error: $message'; 230 | } 231 | 232 | /// An exception thrown when a HTTP/2 connection error occurred. 233 | class TransportConnectionException extends TransportException { 234 | final int errorCode; 235 | 236 | TransportConnectionException(this.errorCode, String details) 237 | : super('Connection error: $details (errorCode: $errorCode)'); 238 | } 239 | 240 | /// An exception thrown when a HTTP/2 stream error occured. 241 | class StreamTransportException extends TransportException { 242 | StreamTransportException(String details) : super('Stream error: $details'); 243 | } 244 | -------------------------------------------------------------------------------- /manual_test/out_of_stream_ids_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE FILE. 4 | 5 | /// --------------------------------------------------------------------------- 6 | /// In order to run this test one needs to change the following line in 7 | /// ../lib/src/streams/stream_handler.dart 8 | /// 9 | /// - static const int MAX_STREAM_ID = (1 << 31) - 1; 10 | /// + static const int MAX_STREAM_ID = (1 << 5) - 1; 11 | /// 12 | /// without this patch this test will run for a _long_ time. 13 | /// --------------------------------------------------------------------------- 14 | library; 15 | 16 | import 'dart:async'; 17 | 18 | import 'package:http2/src/streams/stream_handler.dart'; 19 | import 'package:http2/transport.dart'; 20 | import 'package:test/test.dart'; 21 | 22 | import '../test/transport_test.dart'; 23 | 24 | void main() { 25 | group('transport-test', () { 26 | transportTest('client-runs-out-of-stream-ids', 27 | (ClientTransportConnection client, 28 | ServerTransportConnection server) async { 29 | Future serverFun() async { 30 | await for (ServerTransportStream stream in server.incomingStreams) { 31 | stream.sendHeaders([Header.ascii('x', 'y')], endStream: true); 32 | expect(await stream.incomingMessages.toList(), hasLength(1)); 33 | } 34 | await server.finish(); 35 | } 36 | 37 | Future clientFun() async { 38 | var headers = [Header.ascii('a', 'b')]; 39 | 40 | const kMaxStreamId = StreamHandler.MAX_STREAM_ID; 41 | for (var i = 1; i <= kMaxStreamId; i += 2) { 42 | var stream = client.makeRequest(headers, endStream: true); 43 | var messages = await stream.incomingMessages.toList(); 44 | expect(messages, hasLength(1)); 45 | } 46 | 47 | expect(client.isOpen, false); 48 | expect(() => client.makeRequest(headers), 49 | throwsA(const TypeMatcher())); 50 | 51 | await Future.delayed(const Duration(seconds: 1)); 52 | await client.finish(); 53 | } 54 | 55 | var serverFuture = serverFun(); 56 | var clientFuture = clientFun(); 57 | 58 | await serverFuture; 59 | await clientFuture; 60 | }); 61 | }); 62 | } 63 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: http2 2 | version: 2.3.1-wip 3 | description: A HTTP/2 implementation in Dart. 4 | repository: https://github.com/dart-lang/http2 5 | 6 | topics: 7 | - http 8 | - network 9 | - protocols 10 | 11 | environment: 12 | sdk: ^3.2.0 13 | 14 | dev_dependencies: 15 | build_runner: ^2.3.0 16 | dart_flutter_team_lints: ^2.0.0 17 | mockito: ^5.3.2 18 | test: ^1.21.4 19 | -------------------------------------------------------------------------------- /test/certificates/server_chain.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDKTCCAhGgAwIBAgIJAOWmjTS+OnTEMA0GCSqGSIb3DQEBCwUAMBcxFTATBgNV 3 | BAMMDGludGVybWVkaWF0ZTAeFw0xNTA1MTgwOTAwNDBaFw0yMzA4MDQwOTAwNDBa 4 | MBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC 5 | AQoCggEBALlcwQJuzd+xH8QFgfJSn5tRlvhkldSX98cE7NiA602NBbnAVyUrkRXq 6 | Ni75lgt0kwjYfA9z674m8WSVbgpLPintPCla9CYky1TH0keIs8Rz6cGWHryWEHiu 7 | EDuljQynu2b3sAFuHu9nfWurbJwZnFakBKpdQ9m4EyOZCHC/jHYY7HacKSXg1Cki 8 | we2ca0BWDrcqy8kLy0dZ5oC6IZG8O8drAK8f3f44CRYw59D3sOKBrKXaabpvyEcb 9 | N7Wk2HDBVwHpUJo1reVwtbM8dhqQayYSD8oXnGpP3RQNu/e2rzlXRyq/BfcDY1JI 10 | 7TbC4t/7/N4EcPSpGsTcSOC9A7FpzvECAwEAAaN7MHkwCQYDVR0TBAIwADAsBglg 11 | hkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0O 12 | BBYEFCnwiEMMFZh7NhCr+qA8K0w4Q+AOMB8GA1UdIwQYMBaAFB0h1Evsaw2vfrmS 13 | YuoCTmC4EE6ZMA0GCSqGSIb3DQEBCwUAA4IBAQAcFmHMaXRxyoNaeOowQ6iQWoZd 14 | AUbvG7SHr7I6Pi2aqdqofsKWts7Ytm5WsS0M2nN+sW504houu0iCPeJJX8RQw2q4 15 | CCcNOs9IXk+2uMzlpocHpv+yYoUiD5DxgWh7eghQMLyMpf8FX3Gy4VazeuXznHOM 16 | 4gE4L417xkDzYOzqVTp0FTyAPUv6G2euhNCD6TMru9REcRhYul+K9kocjA5tt2KG 17 | MH6y28LXbLyq4YJUxSUU9gY/xlnbbZS48KDqEcdYC9zjW9nQ0qS+XQuQuFIcwjJ5 18 | V4kAUYxDu6FoTpyQjgsrmBbZlKNxH7Nj4NDlcdJhp/zeSKHqWa5hSWjjKIxp 19 | -----END CERTIFICATE----- 20 | -----BEGIN CERTIFICATE----- 21 | MIIDAjCCAeqgAwIBAgIJAOWmjTS+OnTDMA0GCSqGSIb3DQEBCwUAMBgxFjAUBgNV 22 | BAMMDXJvb3RhdXRob3JpdHkwHhcNMTUwNTE4MDkwMDQwWhcNMjMwODA0MDkwMDQw 23 | WjAXMRUwEwYDVQQDDAxpbnRlcm1lZGlhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IB 24 | DwAwggEKAoIBAQDSrAO1CoPvUllgLOzDm5nG0skDF7vh1DUgAIDVGz0ecD0JFbQx 25 | EF79pju/6MbtpTW2FYvRp11t/G7rGtX923ybOHY/1MNFQrdIvPlO1VV7IGKjoMwP 26 | DNeb0fIGjHoE9QxaDxR8NX8xQbItpsw+TUtRfc9SLkR+jaYJfVRoM21BOncZbSHE 27 | YKiZlEbpecB/+EtwVpgvl+8mPD5U07Fi4fp/lza3WXInXQPyiTVllIEJCt4PKmlu 28 | MocNaJOW38bysL7i0PzDpVZtOxLHOTaW68yF3FckIHNCaA7k1ABEEEegjFMmIao7 29 | B9w7A0jvr4jZVvNmui5Djjn+oJxwEVVgyf8LAgMBAAGjUDBOMB0GA1UdDgQWBBQd 30 | IdRL7GsNr365kmLqAk5guBBOmTAfBgNVHSMEGDAWgBRk81s9d0ZbiZhh44KckwPb 31 | oTc0XzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBZQTK0plfdB5PC 32 | cC5icut4EmrByJa1RbU7ayuEE70e7hla6KVmVjVdCBGltI4jBYwfhKbRItHiAJ/8 33 | x+XZKBG8DLPFuDb7lAa1ObhAYF7YThUFPQYaBhfzKcWrdmWDBFpvNv6E0Mm364dZ 34 | e7Yxmbe5S4agkYPoxEzgEYmcUk9jbjdR6eTbs8laG169ljrECXfEU9RiAcqz5iSX 35 | NLSewqB47hn3B9qgKcQn+PsgO2j7M+rfklhNgeGJeWmy7j6clSOuCsIjWHU0RLQ4 36 | 0W3SB/rpEAJ7fgQbYUPTIUNALSOWi/o1tDX2mXPRjBoxqAv7I+vYk1lZPmSzkyRh 37 | FKvRDxsW 38 | -----END CERTIFICATE----- 39 | -----BEGIN CERTIFICATE----- 40 | MIIDAzCCAeugAwIBAgIJAJ0MomS4Ck+8MA0GCSqGSIb3DQEBCwUAMBgxFjAUBgNV 41 | BAMMDXJvb3RhdXRob3JpdHkwHhcNMTUwNTE4MDkwMDQwWhcNMjMwODA0MDkwMDQw 42 | WjAYMRYwFAYDVQQDDA1yb290YXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOC 43 | AQ8AMIIBCgKCAQEAts1ijtBV92S2cOvpUMOSTp9c6A34nIGr0T5Nhz6XiqRVT+gv 44 | dQgmkdKJQjbvR60y6jzltYFsI2MpGVXY8h/oAL81D/k7PDB2aREgyBfTPAhBHyGw 45 | siR+2xYt5b/Zs99q5RdRqQNzNpLPJriIKvUsRyQWy1UiG2s7pRXQeA8qB0XtJdCj 46 | kFIi+G2bDsaffspGeDOCqt7t+yqvRXfSES0c/l7DIHaiMbbp4//ZNML3RNgAjPz2 47 | hCezZ+wOYajOIyoSPK8IgICrhYFYxvgWxwbLDBEfC5B3jOQsySe10GoRAKZz1gBV 48 | DmgReu81tYJmdgkc9zknnQtIFdA0ex+GvZlfWQIDAQABo1AwTjAdBgNVHQ4EFgQU 49 | ZPNbPXdGW4mYYeOCnJMD26E3NF8wHwYDVR0jBBgwFoAUZPNbPXdGW4mYYeOCnJMD 50 | 26E3NF8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEATzkZ97K777uZ 51 | lQcduNX3ey4IbCiEzFA2zO5Blj+ilfIwNbZXNOgm/lqNvVGDYs6J1apJJe30vL3X 52 | J+t2zsZWzzQzb9uIU37zYemt6m0fHrSrx/iy5lGNqt3HMfqEcOqSCOIK3PCTMz2/ 53 | uyGe1iw33PVeWsm1JUybQ9IrU/huJjbgOHU4wab+8SJCM49ipArp68Fr6j4lcEaE 54 | 4rfRg1ZsvxiOyUB3qPn6wyL/JB8kOJ+QCBe498376eaem8AEFk0kQRh6hDaWtq/k 55 | t6IIXQLjx+EBDVP/veK0UnVhKRP8YTOoV8ZiG1NcdlJmX/Uk7iAfevP7CkBfSN8W 56 | r6AL284qtw== 57 | -----END CERTIFICATE----- 58 | -------------------------------------------------------------------------------- /test/certificates/server_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN ENCRYPTED PRIVATE KEY----- 2 | MIIE5DAcBgoqhkiG9w0BDAEBMA4ECL7L6rj6uEHGAgIIAASCBMLbucyfqAkgCbhP 3 | xNSHYllPMAv/dsIjtnsBwepCXPGkCBCuOAw/2FaCHjN9hBqL5V7fkrKeaemhm2YE 4 | ycPtlHJYPDf3kEkyMjdZ9rIY6kePGfQizs2uJPcXj4YPyQ4HsfVXpOicKfQrouf5 5 | Mze9bGzeMN065q3iP4dYUMwHAyZYteXCsanQNHlqvsWli0W+H8St8fdsXefZhnv1 6 | qVatKWdNdWQ9t5MuljgNU2Vv56sHKEYXI0yLxk2QUMk8KlJfnmt8foYUsnPUXHmc 7 | gIjLKwwVkpdololnEHSNu0cEOUPowjgJru+uMpn7vdNl7TPEQ9jbEgdNg4JwoYzU 8 | 0nao8WzjaSp7kzvZz0VFwKnk5AjstGvvuAWckADdq23QElbn/mF7AG1m/TBpYxzF 9 | gTt37UdndS/AcvVznWVVrRP5iTSIawdIwvqI4s7rqsoE0GCcak+RhchgAz2gWKkS 10 | oODUo0JL6pPVbJ3l4ebbaO6c99nDVc8dViPtc1EkStJEJ2O4kI4xgLSCr4Y9ahKn 11 | oAaoSkX7Xxq3aQm+BzqSpLjdGL8atsqR/YVOIHYIl3gThvP0NfZGx1xHyvO5mCdZ 12 | kHxSA7tKWxauZ3eQ2clbnzeRsl4El0WMHy/5K1ovene4v7sunmoXVtghBC8hK6eh 13 | zMO9orex2PNQ/VQC7HCvtytunOVx1lkSBoNo7hR70igg6rW9H7UyoAoBOwMpT1xa 14 | J6V62nqruTKOqFNfur7aHJGpHGtDb5/ickHeYCyPTvmGp67u4wChzKReeg02oECe 15 | d1E5FKAcIa8s9TVOB6Z+HvTRNQZu2PsI6TJnjQRowvY9DAHiWTlJZBBY/pko3hxX 16 | TsIeybpvRdEHpDWv86/iqtw1hv9CUxS/8ZTWUgBo+osShHW79FeDASr9FC4/Zn76 17 | ZDERTgV4YWlW/klVWcG2lFo7jix+OPXAB+ZQavLhlN1xdWBcIz1AUWjAM4hdPylW 18 | HCX4PB9CQIPl2E7F+Y2p6nMcMWSJVBi5UIH7E9LfaBguXSzMmTk2Fw5p1aOQ6wfN 19 | goVAMVwi8ppAVs741PfHdZ295xMmK/1LCxz5DeAdD/tsA/SYfT753GotioDuC7im 20 | EyJ5JyvTr5I6RFFBuqt3NlUb3Hp16wP3B2x9DZiB6jxr0l341/NHgsyeBXkuIy9j 21 | ON2mvpBPCJhS8kgWo3G0UyyKnx64tcgpGuSvZhGwPz843B6AbYyE6pMRfSWRMkMS 22 | YZYa+VNKhR4ixdj07ocFZEWLVjCH7kxkE8JZXKt8jKYmkWd0lS1QVjgaKlO6lRa3 23 | q6SPJkhW6pvqobvcqVNXwi1XuzpZeEbuh0B7OTekFTTxx5g9XeDl56M8SVQ1KEhT 24 | Q1t7H2Nba18WCB7cf+6PN0F0K0Jz1Kq7ZWaqEI/grX1m4RQuvNF5807sB/QKMO/Z 25 | Gz3NXvHg5xTJRd/567lxPGkor0cE7qD1EZfmJ2HrBYXQ91bhgA7LToBuMZo6ZRXH 26 | QfsanjbP4FPLMiGdQigLjj3A35L/f4sQOOVac/sRaFnm7pzcxsMvyVU/YtvGcjYE 27 | xaOOVnamg661Wo0wksXoDjeSz/JIyyKO3Gwp1FSm2wGLjjy/Ehmqcqy8rvHuf07w 28 | AUukhVtTNn4= 29 | -----END ENCRYPTED PRIVATE KEY----- 30 | -------------------------------------------------------------------------------- /test/multiprotocol_server_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:convert' show ascii, utf8; 7 | import 'dart:io'; 8 | 9 | import 'package:http2/multiprotocol_server.dart'; 10 | import 'package:http2/transport.dart'; 11 | import 'package:test/test.dart'; 12 | 13 | void main() { 14 | var context = SecurityContext() 15 | ..useCertificateChain('test/certificates/server_chain.pem') 16 | ..usePrivateKey('test/certificates/server_key.pem', password: 'dartdart'); 17 | 18 | group('multiprotocol-server', () { 19 | test('http/1.1', () async { 20 | const Count = 2; 21 | 22 | var server = await MultiProtocolHttpServer.bind('localhost', 0, context); 23 | var requestNr = 0; 24 | server.startServing( 25 | expectAsync1((HttpRequest request) async { 26 | await handleHttp11Request(request, requestNr++); 27 | if (requestNr == Count) { 28 | await server.close(); 29 | } 30 | }, count: Count), 31 | expectAsync1((ServerTransportStream stream) {}, count: 0)); 32 | 33 | var client = HttpClient(); 34 | client.badCertificateCallback = (_, __, ___) => true; 35 | for (var i = 0; i < Count; i++) { 36 | await makeHttp11Request(server, client, i); 37 | } 38 | }); 39 | 40 | test('http/2', () async { 41 | const Count = 2; 42 | 43 | var server = await MultiProtocolHttpServer.bind('localhost', 0, context); 44 | var requestNr = 0; 45 | server.startServing( 46 | expectAsync1((HttpRequest request) {}, count: 0), 47 | expectAsync1((ServerTransportStream stream) async { 48 | await handleHttp2Request(stream, requestNr++); 49 | if (requestNr == Count) { 50 | await server.close(); 51 | } 52 | }, count: Count)); 53 | 54 | var socket = await SecureSocket.connect('localhost', server.port, 55 | onBadCertificate: (_) => true, 56 | supportedProtocols: ['http/1.1', 'h2']); 57 | var connection = ClientTransportConnection.viaSocket(socket); 58 | for (var i = 0; i < Count; i++) { 59 | await makeHttp2Request(server, connection, i); 60 | } 61 | await connection.finish(); 62 | }); 63 | }); 64 | } 65 | 66 | Future makeHttp11Request( 67 | MultiProtocolHttpServer server, HttpClient client, int i) async { 68 | var request = 69 | await client.getUrl(Uri.parse('https://localhost:${server.port}/abc$i')); 70 | var response = await request.close(); 71 | var body = await response.cast>().transform(utf8.decoder).join(''); 72 | expect(body, 'answer$i'); 73 | } 74 | 75 | Future handleHttp11Request(HttpRequest request, int i) async { 76 | expect(request.uri.path, '/abc$i'); 77 | await request.drain(); 78 | request.response.write('answer$i'); 79 | await request.response.close(); 80 | } 81 | 82 | Future makeHttp2Request(MultiProtocolHttpServer server, 83 | ClientTransportConnection connection, int i) async { 84 | expect(connection.isOpen, true); 85 | var headers = [ 86 | Header.ascii(':method', 'GET'), 87 | Header.ascii(':scheme', 'https'), 88 | Header.ascii(':authority', 'localhost:${server.port}'), 89 | Header.ascii(':path', '/abc$i'), 90 | ]; 91 | 92 | var stream = connection.makeRequest(headers, endStream: true); 93 | var si = StreamIterator(stream.incomingMessages); 94 | 95 | expect(await si.moveNext(), true); 96 | expect(si.current, isA()); 97 | var responseHeaders = getHeaders(si.current as HeadersStreamMessage); 98 | expect(responseHeaders[':status'], '200'); 99 | 100 | expect(await si.moveNext(), true); 101 | expect(ascii.decode((si.current as DataStreamMessage).bytes), 'answer$i'); 102 | 103 | expect(await si.moveNext(), false); 104 | } 105 | 106 | Future handleHttp2Request(ServerTransportStream stream, int i) async { 107 | var si = StreamIterator(stream.incomingMessages); 108 | 109 | expect(await si.moveNext(), true); 110 | expect(si.current, isA()); 111 | var headers = getHeaders(si.current as HeadersStreamMessage); 112 | 113 | expect(headers[':path'], '/abc$i'); 114 | expect(await si.moveNext(), false); 115 | 116 | stream.outgoingMessages.add(HeadersStreamMessage([ 117 | Header.ascii(':status', '200'), 118 | ])); 119 | 120 | stream.outgoingMessages.add(DataStreamMessage(ascii.encode('answer$i'))); 121 | await stream.outgoingMessages.close(); 122 | } 123 | 124 | Map getHeaders(HeadersStreamMessage headers) { 125 | var map = {}; 126 | for (var h in headers.headers) { 127 | map.putIfAbsent(ascii.decode(h.name), () => ascii.decode(h.value)); 128 | } 129 | return map; 130 | } 131 | -------------------------------------------------------------------------------- /test/server_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:http2/src/connection_preface.dart'; 8 | import 'package:http2/src/frames/frames.dart'; 9 | import 'package:http2/src/hpack/hpack.dart'; 10 | import 'package:http2/src/settings/settings.dart'; 11 | import 'package:http2/transport.dart'; 12 | import 'package:test/test.dart'; 13 | 14 | void main() { 15 | group('server-tests', () { 16 | group('normal', () { 17 | serverTest('gracefull-shutdown-for-unused-connection', 18 | (ServerTransportConnection server, 19 | FrameWriter clientWriter, 20 | StreamIterator clientReader, 21 | Future Function() nextFrame) async { 22 | Future serverFun() async { 23 | expect(await server.incomingStreams.toList(), isEmpty); 24 | await server.finish(); 25 | } 26 | 27 | Future clientFun() async { 28 | expect(await nextFrame() is SettingsFrame, true); 29 | clientWriter.writeSettingsAckFrame(); 30 | clientWriter.writeSettingsFrame([]); 31 | expect(await nextFrame() is SettingsFrame, true); 32 | 33 | // Tell the server to finish. 34 | clientWriter.writeGoawayFrame(3, ErrorCode.NO_ERROR, []); 35 | 36 | // Make sure the server ended the connection. 37 | expect(await clientReader.moveNext(), false); 38 | } 39 | 40 | await Future.wait([serverFun(), clientFun()]); 41 | }); 42 | }); 43 | 44 | group('client-errors', () { 45 | serverTest('no-settings-frame-at-beginning', 46 | (ServerTransportConnection server, 47 | FrameWriter clientWriter, 48 | StreamIterator clientReader, 49 | Future Function() nextFrame) async { 50 | Future serverFun() async { 51 | // TODO: Do we want to get an error in this case? 52 | expect(await server.incomingStreams.toList(), isEmpty); 53 | await server.finish(); 54 | } 55 | 56 | Future clientFun() async { 57 | expect(await nextFrame() is SettingsFrame, true); 58 | 59 | // Write headers frame to open a new stream 60 | clientWriter.writeHeadersFrame(1, [], endStream: true); 61 | 62 | // Make sure the client gets a [GoawayFrame] frame. 63 | expect( 64 | await nextFrame(), 65 | isA().having( 66 | (f) => f.errorCode, 'errorCode', ErrorCode.PROTOCOL_ERROR)); 67 | 68 | // Make sure the server ended the connection. 69 | expect(await clientReader.moveNext(), false); 70 | } 71 | 72 | await Future.wait([serverFun(), clientFun()]); 73 | }); 74 | 75 | serverTest('data-frame-for-invalid-stream', 76 | (ServerTransportConnection server, 77 | FrameWriter clientWriter, 78 | StreamIterator clientReader, 79 | Future Function() nextFrame) async { 80 | Future serverFun() async { 81 | await server.incomingStreams.toList(); 82 | await server.finish(); 83 | } 84 | 85 | Future clientFun() async { 86 | expect(await nextFrame() is SettingsFrame, true); 87 | clientWriter.writeSettingsAckFrame(); 88 | clientWriter.writeSettingsFrame([]); 89 | expect(await nextFrame() is SettingsFrame, true); 90 | 91 | // Write data frame to non-existent stream. 92 | clientWriter.writeDataFrame(3, [1, 2, 3]); 93 | 94 | // Make sure the client gets a [RstStreamFrame] frame. 95 | var frame = await nextFrame(); 96 | expect(frame is WindowUpdateFrame, true); 97 | expect( 98 | await nextFrame(), 99 | isA() 100 | .having( 101 | (f) => f.errorCode, 'errorCode', ErrorCode.STREAM_CLOSED) 102 | .having((f) => f.header.streamId, 'header.streamId', 3)); 103 | 104 | // Tell the server to finish. 105 | clientWriter.writeGoawayFrame(3, ErrorCode.NO_ERROR, []); 106 | 107 | // Make sure the server ended the connection. 108 | expect(await clientReader.moveNext(), false); 109 | } 110 | 111 | await Future.wait([serverFun(), clientFun()]); 112 | }); 113 | 114 | serverTest('data-frame-after-stream-closed', 115 | (ServerTransportConnection server, 116 | FrameWriter clientWriter, 117 | StreamIterator clientReader, 118 | Future Function() nextFrame) async { 119 | Future serverFun() async { 120 | await server.incomingStreams.toList(); 121 | await server.finish(); 122 | } 123 | 124 | Future clientFun() async { 125 | expect(await nextFrame() is SettingsFrame, true); 126 | clientWriter.writeSettingsAckFrame(); 127 | clientWriter.writeSettingsFrame([]); 128 | expect(await nextFrame() is SettingsFrame, true); 129 | 130 | clientWriter.writeHeadersFrame(3, [Header.ascii('a', 'b')], 131 | endStream: true); 132 | 133 | // Write data frame to non-existent stream (stream 3 was closed 134 | // above). 135 | clientWriter.writeDataFrame(3, [1, 2, 3]); 136 | 137 | // Make sure the client gets a [RstStreamFrame] frame. 138 | expect( 139 | await nextFrame(), 140 | isA() 141 | .having( 142 | (f) => f.errorCode, 'errorCode', ErrorCode.STREAM_CLOSED) 143 | .having((f) => f.header.streamId, 'header.streamId', 3)); 144 | 145 | // Tell the server to finish. 146 | clientWriter.writeGoawayFrame(3, ErrorCode.NO_ERROR, []); 147 | 148 | // Make sure the server ended the connection. 149 | expect(await clientReader.moveNext(), false); 150 | } 151 | 152 | await Future.wait([serverFun(), clientFun()]); 153 | }); 154 | }); 155 | 156 | group('server-errors', () { 157 | serverTest('server-resets-stream', (ServerTransportConnection server, 158 | FrameWriter clientWriter, 159 | StreamIterator clientReader, 160 | Future Function() nextFrame) async { 161 | Future serverFun() async { 162 | var it = StreamIterator(server.incomingStreams); 163 | expect(await it.moveNext(), true); 164 | 165 | TransportStream stream = it.current; 166 | stream.terminate(); 167 | 168 | expect(await it.moveNext(), false); 169 | 170 | await server.finish(); 171 | } 172 | 173 | Future clientFun() async { 174 | expect(await nextFrame() is SettingsFrame, true); 175 | clientWriter.writeSettingsAckFrame(); 176 | clientWriter.writeSettingsFrame([]); 177 | expect(await nextFrame() is SettingsFrame, true); 178 | 179 | clientWriter.writeHeadersFrame(1, [Header.ascii('a', 'b')], 180 | endStream: false); 181 | 182 | // Make sure the client gets a [RstStreamFrame] frame. 183 | expect( 184 | await nextFrame(), 185 | isA() 186 | .having((f) => f.errorCode, 'errorCode', ErrorCode.CANCEL) 187 | .having((f) => f.header.streamId, 'header.streamId', 1)); 188 | 189 | // Tell the server to finish. 190 | clientWriter.writeGoawayFrame(3, ErrorCode.NO_ERROR, []); 191 | 192 | // Make sure the server ended the connection. 193 | expect(await clientReader.moveNext(), false); 194 | } 195 | 196 | await Future.wait([serverFun(), clientFun()]); 197 | }); 198 | }); 199 | }); 200 | } 201 | 202 | void serverTest( 203 | String name, 204 | void Function( 205 | ServerTransportConnection, 206 | FrameWriter, 207 | StreamIterator frameReader, 208 | Future Function() readNext) 209 | func) { 210 | return test(name, () { 211 | var streams = ClientErrorStreams(); 212 | var clientReader = streams.clientConnectionFrameReader; 213 | 214 | Future readNext() async { 215 | expect(await clientReader.moveNext(), true); 216 | return clientReader.current; 217 | } 218 | 219 | return func(streams.serverConnection, streams.clientConnectionFrameWriter, 220 | clientReader, readNext); 221 | }); 222 | } 223 | 224 | class ClientErrorStreams { 225 | final StreamController> writeA = StreamController(); 226 | final StreamController> writeB = StreamController(); 227 | Stream> get readA => writeA.stream; 228 | Stream> get readB => writeB.stream; 229 | 230 | StreamIterator get clientConnectionFrameReader { 231 | var localSettings = ActiveSettings(); 232 | return StreamIterator(FrameReader(readA, localSettings).startDecoding()); 233 | } 234 | 235 | FrameWriter get clientConnectionFrameWriter { 236 | var encoder = HPackEncoder(); 237 | var peerSettings = ActiveSettings(); 238 | writeB.add(CONNECTION_PREFACE); 239 | return FrameWriter(encoder, writeB, peerSettings); 240 | } 241 | 242 | ServerTransportConnection get serverConnection => 243 | ServerTransportConnection.viaStreams(readB, writeA); 244 | } 245 | -------------------------------------------------------------------------------- /test/src/async_utils/async_utils_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:http2/src/async_utils/async_utils.dart'; 8 | import 'package:test/test.dart'; 9 | 10 | void main() { 11 | group('async_utils', () { 12 | test('buffer-indicator', () { 13 | var bi = BufferIndicator(); 14 | bi.bufferEmptyEvents.listen(expectAsync1((_) {}, count: 2)); 15 | 16 | expect(bi.wouldBuffer, true); 17 | 18 | bi.markUnBuffered(); 19 | expect(bi.wouldBuffer, false); 20 | 21 | bi.markBuffered(); 22 | expect(bi.wouldBuffer, true); 23 | bi.markBuffered(); 24 | expect(bi.wouldBuffer, true); 25 | 26 | bi.markUnBuffered(); 27 | expect(bi.wouldBuffer, false); 28 | bi.markUnBuffered(); 29 | expect(bi.wouldBuffer, false); 30 | 31 | bi.markBuffered(); 32 | expect(bi.wouldBuffer, true); 33 | bi.markBuffered(); 34 | expect(bi.wouldBuffer, true); 35 | }); 36 | 37 | test('buffered-sink', () { 38 | var c = StreamController>(); 39 | var bs = BufferedSink(c); 40 | 41 | expect(bs.bufferIndicator.wouldBuffer, true); 42 | var sub = c.stream.listen(expectAsync1((_) {}, count: 2)); 43 | 44 | expect(bs.bufferIndicator.wouldBuffer, false); 45 | 46 | sub.pause(); 47 | Timer.run(expectAsync0(() { 48 | expect(bs.bufferIndicator.wouldBuffer, true); 49 | bs.sink.add([1]); 50 | 51 | sub.resume(); 52 | Timer.run(expectAsync0(() { 53 | expect(bs.bufferIndicator.wouldBuffer, false); 54 | bs.sink.add([2]); 55 | 56 | Timer.run(expectAsync0(() { 57 | sub.cancel(); 58 | expect(bs.bufferIndicator.wouldBuffer, false); 59 | })); 60 | })); 61 | })); 62 | }); 63 | 64 | test('buffered-bytes-writer', () async { 65 | var c = StreamController>(); 66 | var writer = BufferedBytesWriter(c); 67 | 68 | expect(writer.bufferIndicator.wouldBuffer, true); 69 | 70 | var bytesFuture = c.stream.fold>([], (b, d) => b..addAll(d)); 71 | 72 | expect(writer.bufferIndicator.wouldBuffer, false); 73 | 74 | writer.add([1, 2]); 75 | writer.add([3, 4]); 76 | 77 | writer.addBufferedData([5, 6]); 78 | expect(() => writer.add([7, 8]), throwsStateError); 79 | 80 | writer.addBufferedData([7, 8]); 81 | await writer.close(); 82 | expect(await bytesFuture, [1, 2, 3, 4, 5, 6, 7, 8]); 83 | }); 84 | }); 85 | } 86 | -------------------------------------------------------------------------------- /test/src/connection_preface_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:math' show min; 7 | 8 | import 'package:http2/src/connection_preface.dart'; 9 | import 'package:test/test.dart'; 10 | 11 | void main() { 12 | group('connection-preface', () { 13 | test('successful', () async { 14 | final frameBytes = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 15 | final data = List.from(CONNECTION_PREFACE)..addAll(frameBytes); 16 | 17 | for (var size = 1; size <= data.length; size++) { 18 | var c = StreamController>(); 19 | var resultF = readConnectionPreface(c.stream) 20 | .fold>([], (b, d) => b..addAll(d)); 21 | 22 | for (var i = 0; i < (size - 1 + data.length) ~/ size; i++) { 23 | var from = size * i; 24 | var to = min(size * (i + 1), data.length); 25 | 26 | c.add(data.sublist(from, to)); 27 | } 28 | unawaited(c.close()); 29 | 30 | expect(await resultF, frameBytes); 31 | } 32 | }); 33 | 34 | test('only-part-of-connection-sequence', () async { 35 | var c = StreamController>(); 36 | var resultF = readConnectionPreface(c.stream) 37 | .fold>([], (b, d) => b..addAll(d)); 38 | 39 | for (var i = 0; i < CONNECTION_PREFACE.length - 1; i++) { 40 | c.add([CONNECTION_PREFACE[i]]); 41 | } 42 | unawaited(c.close()); 43 | 44 | unawaited(resultF.catchError(expectAsync2((Object error, Object _) { 45 | expect(error, contains('EOS before connection preface could be read')); 46 | return []; 47 | }))); 48 | }); 49 | 50 | test('wrong-connection-sequence', () async { 51 | var c = StreamController>(); 52 | var resultF = readConnectionPreface(c.stream) 53 | .fold>([], (b, d) => b..addAll(d)); 54 | 55 | for (var i = 0; i < CONNECTION_PREFACE.length; i++) { 56 | c.add([0xff]); 57 | } 58 | unawaited(c.close()); 59 | 60 | unawaited(resultF.catchError(expectAsync2((Object error, Object _) { 61 | expect(error, contains('Connection preface does not match.')); 62 | return []; 63 | }))); 64 | }); 65 | 66 | test('incoming-socket-error', () async { 67 | var c = StreamController>(); 68 | var resultF = readConnectionPreface(c.stream) 69 | .fold>([], (b, d) => b..addAll(d)); 70 | 71 | c.addError('hello world'); 72 | unawaited(c.close()); 73 | 74 | unawaited(resultF.catchError(expectAsync2((Object error, Object _) { 75 | expect(error, contains('hello world')); 76 | return []; 77 | }))); 78 | }); 79 | }); 80 | } 81 | -------------------------------------------------------------------------------- /test/src/error_matchers.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:http2/src/sync_errors.dart'; 6 | import 'package:test/test.dart'; 7 | 8 | const Matcher isProtocolException = TypeMatcher(); 9 | const Matcher isFrameSizeException = TypeMatcher(); 10 | const Matcher isTerminatedException = TypeMatcher(); 11 | const Matcher isFlowControlException = TypeMatcher(); 12 | -------------------------------------------------------------------------------- /test/src/flowcontrol/connection_queues_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:http2/src/async_utils/async_utils.dart'; 6 | import 'package:http2/src/flowcontrol/connection_queues.dart'; 7 | import 'package:http2/src/flowcontrol/queue_messages.dart'; 8 | import 'package:http2/src/flowcontrol/window.dart'; 9 | import 'package:http2/src/flowcontrol/window_handler.dart'; 10 | import 'package:http2/src/frames/frames.dart'; 11 | import 'package:http2/transport.dart'; 12 | import 'package:mockito/mockito.dart'; 13 | import 'package:test/test.dart'; 14 | 15 | import 'mocks.mocks.dart'; 16 | 17 | void main() { 18 | group('flowcontrol', () { 19 | test('connection-message-queue-out', () { 20 | var fw = MockFrameWriter(); 21 | when(fw.bufferIndicator).thenReturn(BufferIndicator()); 22 | when(fw.writeHeadersFrame(any, any, endStream: anyNamed('endStream'))) 23 | .thenReturn(null); 24 | when(fw.writeDataFrame(any, any, endStream: anyNamed('endStream'))) 25 | .thenReturn(null); 26 | var windowMock = MockOutgoingWindowHandler(); 27 | var queue = ConnectionMessageQueueOut(windowMock, fw); 28 | 29 | fw.bufferIndicator.markUnBuffered(); 30 | 31 | expect(queue.pendingMessages, 0); 32 | 33 | var headers = [Header.ascii('a', 'b')]; 34 | var bytes = [1, 2, 3]; 35 | 36 | // Send [HeadersMessage]. 37 | queue.enqueueMessage(HeadersMessage(99, headers, false)); 38 | expect(queue.pendingMessages, 0); 39 | verify(fw.writeHeadersFrame(99, headers, endStream: false)).called(1); 40 | verify(fw.bufferIndicator).called(greaterThan(0)); 41 | verifyNoMoreInteractions(fw); 42 | verifyZeroInteractions(windowMock); 43 | 44 | clearInteractions(fw); 45 | 46 | // Send [DataMessage]. 47 | windowMock.peerWindowSize = bytes.length; 48 | windowMock.positiveWindow.markUnBuffered(); 49 | queue.enqueueMessage(DataMessage(99, bytes, false)); 50 | expect(queue.pendingMessages, 0); 51 | verify(windowMock.decreaseWindow(bytes.length)).called(1); 52 | verify(fw.writeDataFrame(99, bytes, endStream: false)).called(1); 53 | verifyNoMoreInteractions(windowMock); 54 | verify(fw.bufferIndicator).called(greaterThan(0)); 55 | verifyNoMoreInteractions(fw); 56 | 57 | clearInteractions(fw); 58 | clearInteractions(windowMock); 59 | 60 | // Send [DataMessage] if the connection window is too small. 61 | // Should trigger fragmentation and should write 1 byte. 62 | windowMock.peerWindowSize = 1; 63 | // decreaseWindow() marks the window as buffered in this case, so we need 64 | // our mock to do the same (otherwise, the call to markUnBuffered() below 65 | // has no effect). 66 | when(windowMock.decreaseWindow(1)).thenAnswer((_) { 67 | windowMock.positiveWindow.markBuffered(); 68 | }); 69 | queue.enqueueMessage(DataMessage(99, bytes, true)); 70 | expect(queue.pendingMessages, 1); 71 | verify(windowMock.decreaseWindow(1)).called(1); 72 | verify(fw.bufferIndicator).called(greaterThan(0)); 73 | verify(fw.writeDataFrame(99, bytes.sublist(0, 1), endStream: false)) 74 | .called(1); 75 | verifyNoMoreInteractions(windowMock); 76 | verifyNoMoreInteractions(fw); 77 | 78 | clearInteractions(fw); 79 | reset(windowMock); 80 | 81 | // Now mark it as unbuffered. This should write the rest of the 82 | // [bytes.length - 1] bytes. 83 | windowMock.peerWindowSize = bytes.length - 1; 84 | windowMock.positiveWindow.markUnBuffered(); 85 | verify(windowMock.decreaseWindow(bytes.length - 1)).called(1); 86 | verify(fw.writeDataFrame(99, bytes.sublist(1), endStream: true)) 87 | .called(1); 88 | verifyNoMoreInteractions(windowMock); 89 | verify(fw.bufferIndicator).called(greaterThan(0)); 90 | verifyNoMoreInteractions(fw); 91 | 92 | queue.startClosing(); 93 | queue.done.then(expectAsync1((_) { 94 | expect(queue.pendingMessages, 0); 95 | expect(() => queue.enqueueMessage(DataMessage(99, bytes, true)), 96 | throwsA(const TypeMatcher())); 97 | })); 98 | }); 99 | 100 | test('connection-message-queue-in', () { 101 | const STREAM_ID = 99; 102 | final bytes = [1, 2, 3]; 103 | 104 | var windowMock = MockIncomingWindowHandler(); 105 | when(windowMock.gotData(any)).thenReturn(null); 106 | when(windowMock.dataProcessed(any)).thenReturn(null); 107 | 108 | var queue = ConnectionMessageQueueIn(windowMock, (f) => f()); 109 | expect(queue.pendingMessages, 0); 110 | 111 | var streamQueueMock = MockStreamMessageQueueIn(); 112 | when(streamQueueMock.bufferIndicator).thenReturn(BufferIndicator()); 113 | when(streamQueueMock.enqueueMessage(any)).thenReturn(null); 114 | 115 | queue.insertNewStreamMessageQueue(STREAM_ID, streamQueueMock); 116 | 117 | // Insert a [DataFrame] and let it be buffered. 118 | var header = FrameHeader(0, 0, 0, STREAM_ID); 119 | queue.processDataFrame(DataFrame(header, 0, bytes)); 120 | expect(queue.pendingMessages, 1); 121 | verify(windowMock.gotData(bytes.length)).called(1); 122 | verifyNoMoreInteractions(windowMock); 123 | verify(streamQueueMock.bufferIndicator).called(greaterThan(0)); 124 | verifyNoMoreInteractions(streamQueueMock); 125 | 126 | clearInteractions(windowMock); 127 | 128 | // Indicate that the stream queue has space, and make sure 129 | // the data is propagated from the connection to the stream 130 | // specific queue. 131 | streamQueueMock.bufferIndicator.markUnBuffered(); 132 | verify(windowMock.dataProcessed(bytes.length)).called(1); 133 | var capturedMessage = verify(streamQueueMock.enqueueMessage(captureAny)) 134 | .captured 135 | .single as DataMessage; 136 | expect(capturedMessage.streamId, STREAM_ID); 137 | expect(capturedMessage.bytes, bytes); 138 | 139 | verifyNoMoreInteractions(windowMock); 140 | verify(streamQueueMock.bufferIndicator).called(greaterThan(0)); 141 | verifyNoMoreInteractions(streamQueueMock); 142 | 143 | // TODO: Write tests for adding HeadersFrame/PushPromiseFrame. 144 | }); 145 | 146 | test('connection-ignored-message-queue-in', () { 147 | const STREAM_ID = 99; 148 | final bytes = [1, 2, 3]; 149 | 150 | var windowMock = MockIncomingWindowHandler(); 151 | when(windowMock.gotData(any)).thenReturn(null); 152 | var queue = ConnectionMessageQueueIn(windowMock, (f) => f()); 153 | 154 | // Insert a [DataFrame] and let it be buffered. 155 | var header = FrameHeader(0, 0, 0, STREAM_ID); 156 | queue.processIgnoredDataFrame(DataFrame(header, 0, bytes)); 157 | expect(queue.pendingMessages, 0); 158 | verify(windowMock.dataProcessed(bytes.length)).called(1); 159 | verifyNoMoreInteractions(windowMock); 160 | }); 161 | }); 162 | } 163 | 164 | class MockOutgoingWindowHandler extends Mock 165 | implements OutgoingConnectionWindowHandler, OutgoingStreamWindowHandler { 166 | @override 167 | BufferIndicator positiveWindow = BufferIndicator(); 168 | @override 169 | int peerWindowSize = Window().size; 170 | } 171 | -------------------------------------------------------------------------------- /test/src/flowcontrol/mocks.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:http2/src/flowcontrol/connection_queues.dart'; 6 | import 'package:http2/src/flowcontrol/stream_queues.dart'; 7 | import 'package:http2/src/flowcontrol/window_handler.dart'; 8 | import 'package:http2/src/frames/frames.dart'; 9 | import 'package:mockito/annotations.dart'; 10 | 11 | @GenerateMocks([ 12 | FrameWriter, 13 | IncomingWindowHandler, 14 | OutgoingStreamWindowHandler, 15 | ], customMocks: [ 16 | MockSpec(fallbackGenerators: { 17 | #ensureNotTerminatedSync: ensureNotTerminatedSyncFallback, 18 | }), 19 | MockSpec(fallbackGenerators: { 20 | #ensureNotTerminatedSync: ensureNotTerminatedSyncFallback, 21 | }) 22 | ]) 23 | T ensureNotTerminatedSyncFallback(T Function()? f) => 24 | throw UnimplementedError( 25 | 'Method cannot be stubbed; requires fallback values for return'); 26 | -------------------------------------------------------------------------------- /test/src/flowcontrol/stream_queues_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:http2/src/async_utils/async_utils.dart'; 6 | import 'package:http2/src/flowcontrol/queue_messages.dart'; 7 | import 'package:http2/src/flowcontrol/stream_queues.dart'; 8 | import 'package:http2/transport.dart'; 9 | import 'package:mockito/mockito.dart'; 10 | import 'package:test/test.dart'; 11 | 12 | import 'mocks.mocks.dart'; 13 | 14 | void main() { 15 | group('flowcontrol', () { 16 | const STREAM_ID = 99; 17 | const BYTES = [1, 2, 3]; 18 | 19 | group('stream-message-queue-out', () { 20 | test('window-big-enough', () { 21 | var connectionQueueMock = MockConnectionMessageQueueOut(); 22 | when(connectionQueueMock.enqueueMessage(any)).thenReturn(null); 23 | var windowMock = MockOutgoingStreamWindowHandler(); 24 | when(windowMock.positiveWindow).thenReturn(BufferIndicator()); 25 | when(windowMock.decreaseWindow(any)).thenReturn(null); 26 | 27 | windowMock.positiveWindow.markUnBuffered(); 28 | var queue = 29 | StreamMessageQueueOut(STREAM_ID, windowMock, connectionQueueMock); 30 | 31 | expect(queue.bufferIndicator.wouldBuffer, isFalse); 32 | expect(queue.pendingMessages, 0); 33 | when(windowMock.peerWindowSize).thenReturn(BYTES.length); 34 | 35 | queue.enqueueMessage(DataMessage(STREAM_ID, BYTES, true)); 36 | verify(windowMock.decreaseWindow(BYTES.length)).called(1); 37 | final capturedMessage = 38 | verify(connectionQueueMock.enqueueMessage(captureAny)) 39 | .captured 40 | .single; 41 | expect(capturedMessage, const TypeMatcher()); 42 | var capturedDataMessage = capturedMessage as DataMessage; 43 | expect(capturedDataMessage.bytes, BYTES); 44 | expect(capturedDataMessage.endStream, isTrue); 45 | }); 46 | 47 | test('window-smaller-than-necessary', () { 48 | var connectionQueueMock = MockConnectionMessageQueueOut(); 49 | when(connectionQueueMock.enqueueMessage(any)).thenReturn(null); 50 | var windowMock = MockOutgoingStreamWindowHandler(); 51 | when(windowMock.positiveWindow).thenReturn(BufferIndicator()); 52 | when(windowMock.decreaseWindow(any)).thenReturn(null); 53 | windowMock.positiveWindow.markUnBuffered(); 54 | var queue = 55 | StreamMessageQueueOut(STREAM_ID, windowMock, connectionQueueMock); 56 | 57 | expect(queue.bufferIndicator.wouldBuffer, isFalse); 58 | expect(queue.pendingMessages, 0); 59 | 60 | // We set the window size fixed to 1, which means all the data messages 61 | // will get fragmented to 1 byte. 62 | when(windowMock.peerWindowSize).thenReturn(1); 63 | queue.enqueueMessage(DataMessage(STREAM_ID, BYTES, true)); 64 | 65 | expect(queue.pendingMessages, 0); 66 | verify(windowMock.decreaseWindow(1)).called(BYTES.length); 67 | final messages = 68 | verify(connectionQueueMock.enqueueMessage(captureAny)).captured; 69 | expect(messages, hasLength(BYTES.length)); 70 | for (var counter = 0; counter < messages.length; counter++) { 71 | expect(messages[counter], const TypeMatcher()); 72 | var dataMessage = messages[counter] as DataMessage; 73 | expect(dataMessage.bytes, BYTES.sublist(counter, counter + 1)); 74 | expect(dataMessage.endStream, counter == BYTES.length - 1); 75 | } 76 | verify(windowMock.positiveWindow).called(greaterThan(0)); 77 | verify(windowMock.peerWindowSize).called(greaterThan(0)); 78 | verifyNoMoreInteractions(windowMock); 79 | }); 80 | 81 | test('window-empty', () { 82 | var connectionQueueMock = MockConnectionMessageQueueOut(); 83 | var windowMock = MockOutgoingStreamWindowHandler(); 84 | when(windowMock.positiveWindow).thenReturn(BufferIndicator()); 85 | windowMock.positiveWindow.markUnBuffered(); 86 | var queue = 87 | StreamMessageQueueOut(STREAM_ID, windowMock, connectionQueueMock); 88 | 89 | expect(queue.bufferIndicator.wouldBuffer, isFalse); 90 | expect(queue.pendingMessages, 0); 91 | 92 | when(windowMock.peerWindowSize).thenReturn(0); 93 | queue.enqueueMessage(DataMessage(STREAM_ID, BYTES, true)); 94 | expect(queue.bufferIndicator.wouldBuffer, isTrue); 95 | expect(queue.pendingMessages, 1); 96 | verify(windowMock.positiveWindow).called(greaterThan(0)); 97 | verify(windowMock.peerWindowSize).called(greaterThan(0)); 98 | verifyNoMoreInteractions(windowMock); 99 | verifyZeroInteractions(connectionQueueMock); 100 | }); 101 | }); 102 | 103 | group('stream-message-queue-in', () { 104 | test('data-end-of-stream', () { 105 | var windowMock = MockIncomingWindowHandler(); 106 | when(windowMock.gotData(any)).thenReturn(null); 107 | when(windowMock.dataProcessed(any)).thenReturn(null); 108 | var queue = StreamMessageQueueIn(windowMock); 109 | 110 | expect(queue.pendingMessages, 0); 111 | queue.messages.listen(expectAsync1((StreamMessage message) { 112 | expect(message, isA()); 113 | 114 | var dataMessage = message as DataStreamMessage; 115 | expect(dataMessage.bytes, BYTES); 116 | }), onDone: expectAsync0(() {})); 117 | queue.enqueueMessage(DataMessage(STREAM_ID, BYTES, true)); 118 | expect(queue.bufferIndicator.wouldBuffer, isFalse); 119 | verifyInOrder([ 120 | windowMock.gotData(BYTES.length), 121 | windowMock.dataProcessed(BYTES.length) 122 | ]); 123 | verifyNoMoreInteractions(windowMock); 124 | }); 125 | }); 126 | 127 | test('data-end-of-stream--paused', () { 128 | const STREAM_ID = 99; 129 | final bytes = [1, 2, 3]; 130 | 131 | var windowMock = MockIncomingWindowHandler(); 132 | when(windowMock.gotData(any)).thenReturn(null); 133 | var queue = StreamMessageQueueIn(windowMock); 134 | 135 | var sub = queue.messages.listen(expectAsync1((_) {}, count: 0), 136 | onDone: expectAsync0(() {}, count: 0)); 137 | sub.pause(); 138 | 139 | expect(queue.pendingMessages, 0); 140 | queue.enqueueMessage(DataMessage(STREAM_ID, bytes, true)); 141 | expect(queue.pendingMessages, 1); 142 | expect(queue.bufferIndicator.wouldBuffer, isTrue); 143 | // We assert that we got the data, but it wasn't processed. 144 | verify(windowMock.gotData(bytes.length)).called(1); 145 | // verifyNever(windowMock.dataProcessed(any)); 146 | }); 147 | 148 | // TODO: Add tests for Headers/HeadersPush messages. 149 | }); 150 | } 151 | -------------------------------------------------------------------------------- /test/src/flowcontrol/window_handler_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:http2/src/flowcontrol/window.dart'; 6 | import 'package:http2/src/flowcontrol/window_handler.dart'; 7 | import 'package:http2/src/frames/frames.dart'; 8 | import 'package:mockito/mockito.dart'; 9 | import 'package:test/test.dart'; 10 | 11 | import '../error_matchers.dart'; 12 | 13 | void main() { 14 | group('flowcontrol', () { 15 | void testAbstractOutgoingWindowHandler( 16 | AbstractOutgoingWindowHandler handler, Window window, int initialSize) { 17 | var sub = handler.positiveWindow.bufferEmptyEvents 18 | .listen(expectAsync1((_) {}, count: 0)); 19 | 20 | expect(handler.peerWindowSize, initialSize); 21 | expect(window.size, initialSize); 22 | 23 | // If we're sending data to the remote end, we need to subtract 24 | // the number of bytes from the outgoing connection (and stream) windows. 25 | handler.decreaseWindow(100); 26 | expect(handler.peerWindowSize, initialSize - 100); 27 | expect(window.size, initialSize - 100); 28 | 29 | // If we received a window update frame, the window should be increased 30 | // again. 31 | var frameHeader = FrameHeader(4, FrameType.WINDOW_UPDATE, 0, 0); 32 | handler.processWindowUpdate(WindowUpdateFrame(frameHeader, 100)); 33 | expect(handler.peerWindowSize, initialSize); 34 | expect(window.size, initialSize); 35 | 36 | sub.cancel(); 37 | 38 | // If we decrease the outgoing window size to 0 or below, and 39 | // increase it again, we expect to get an update event. 40 | expect(handler.positiveWindow.wouldBuffer, isFalse); 41 | handler.decreaseWindow(window.size); 42 | expect(handler.positiveWindow.wouldBuffer, isTrue); 43 | sub = handler.positiveWindow.bufferEmptyEvents.listen(expectAsync1((_) { 44 | expect(handler.peerWindowSize, 1); 45 | expect(window.size, 1); 46 | })); 47 | 48 | // Now we trigger the 1 byte window increase 49 | handler.processWindowUpdate(WindowUpdateFrame(frameHeader, 1)); 50 | sub.cancel(); 51 | 52 | // If the remote end sends us [WindowUpdateFrame]s which increase it above 53 | // the maximum size, we throw a [FlowControlException]. 54 | var frame = WindowUpdateFrame(frameHeader, Window.MAX_WINDOW_SIZE); 55 | expect(() => handler.processWindowUpdate(frame), 56 | throwsA(isFlowControlException)); 57 | } 58 | 59 | test('outgoing-connection-window-handler', () { 60 | var window = Window(); 61 | var initialSize = window.size; 62 | var handler = OutgoingConnectionWindowHandler(window); 63 | 64 | testAbstractOutgoingWindowHandler(handler, window, initialSize); 65 | }); 66 | 67 | test('outgoing-stream-window-handler', () { 68 | var window = Window(); 69 | var initialSize = window.size; 70 | var handler = OutgoingStreamWindowHandler(window); 71 | 72 | testAbstractOutgoingWindowHandler(handler, window, initialSize); 73 | 74 | // Test stream specific functionality: If the connection window 75 | // gets increased/decreased via a [SettingsFrame], all stream 76 | // windows need to get updated as well. 77 | 78 | window = Window(); 79 | initialSize = window.size; 80 | handler = OutgoingStreamWindowHandler(window); 81 | 82 | expect(handler.positiveWindow.wouldBuffer, isFalse); 83 | final bufferEmpty = handler.positiveWindow.bufferEmptyEvents 84 | .listen(expectAsync1((_) {}, count: 0)); 85 | handler.processInitialWindowSizeSettingChange(-window.size); 86 | expect(handler.positiveWindow.wouldBuffer, isTrue); 87 | expect(handler.peerWindowSize, 0); 88 | expect(window.size, 0); 89 | bufferEmpty.onData(expectAsync1((_) {}, count: 1)); 90 | handler.processInitialWindowSizeSettingChange(1); 91 | expect(handler.positiveWindow.wouldBuffer, isFalse); 92 | expect(handler.peerWindowSize, 1); 93 | expect(window.size, 1); 94 | 95 | expect( 96 | () => handler.processInitialWindowSizeSettingChange( 97 | Window.MAX_WINDOW_SIZE + 1), 98 | throwsA(isFlowControlException)); 99 | }); 100 | 101 | test('incoming-window-handler', () { 102 | const STREAM_ID = 99; 103 | 104 | var fw = FrameWriterMock(); 105 | var window = Window(); 106 | var initialSize = window.size; 107 | var handler = IncomingWindowHandler.stream(fw, window, STREAM_ID); 108 | 109 | expect(handler.localWindowSize, initialSize); 110 | expect(window.size, initialSize); 111 | 112 | // If the remote end sends us now 100 bytes, it reduces the local 113 | // incoming window by 100 bytes. Once we handled these bytes, it, 114 | // will send a [WindowUpdateFrame] to the remote peer to ACK it. 115 | handler.gotData(100); 116 | expect(handler.localWindowSize, initialSize - 100); 117 | expect(window.size, initialSize - 100); 118 | 119 | // The data might sit in a queue. Once the user drains enough data of 120 | // the queue, we will start ACKing the data and the window becomes 121 | // positive again. 122 | handler.dataProcessed(100); 123 | expect(handler.localWindowSize, initialSize); 124 | expect(window.size, initialSize); 125 | verify(fw.writeWindowUpdate(100, streamId: STREAM_ID)).called(1); 126 | verifyNoMoreInteractions(fw); 127 | }); 128 | }); 129 | } 130 | 131 | class FrameWriterMock extends Mock implements FrameWriter {} 132 | -------------------------------------------------------------------------------- /test/src/frames/frame_defragmenter_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:http2/src/frames/frame_defragmenter.dart'; 6 | import 'package:http2/src/frames/frames.dart'; 7 | import 'package:test/test.dart'; 8 | 9 | import '../error_matchers.dart'; 10 | 11 | void main() { 12 | group('frames', () { 13 | group('frame-defragmenter', () { 14 | UnknownFrame unknownFrame() { 15 | return UnknownFrame(FrameHeader(0, 0, 0, 1), []); 16 | } 17 | 18 | HeadersFrame headersFrame(List data, 19 | {bool fragmented = false, int streamId = 1}) { 20 | var flags = fragmented ? 0 : HeadersFrame.FLAG_END_HEADERS; 21 | var header = 22 | FrameHeader(data.length, FrameType.HEADERS, flags, streamId); 23 | return HeadersFrame(header, 0, false, null, null, data); 24 | } 25 | 26 | PushPromiseFrame pushPromiseFrame(List data, 27 | {bool fragmented = false, int streamId = 1}) { 28 | var flags = fragmented ? 0 : HeadersFrame.FLAG_END_HEADERS; 29 | var header = 30 | FrameHeader(data.length, FrameType.PUSH_PROMISE, flags, streamId); 31 | return PushPromiseFrame(header, 0, 44, data); 32 | } 33 | 34 | ContinuationFrame continuationFrame(List data, 35 | {bool fragmented = false, int streamId = 1}) { 36 | var flags = fragmented ? 0 : ContinuationFrame.FLAG_END_HEADERS; 37 | var header = 38 | FrameHeader(data.length, FrameType.CONTINUATION, flags, streamId); 39 | return ContinuationFrame(header, data); 40 | } 41 | 42 | test('unknown-frame', () { 43 | var defrag = FrameDefragmenter(); 44 | expect(defrag.tryDefragmentFrame(unknownFrame()) is UnknownFrame, true); 45 | }); 46 | 47 | test('fragmented-headers-frame', () { 48 | var defrag = FrameDefragmenter(); 49 | 50 | var f1 = headersFrame([1, 2, 3], fragmented: true); 51 | var f2 = continuationFrame([4, 5, 6], fragmented: true); 52 | var f3 = continuationFrame([7, 8, 9], fragmented: false); 53 | 54 | expect(defrag.tryDefragmentFrame(f1), isNull); 55 | expect(defrag.tryDefragmentFrame(f2), isNull); 56 | var h = defrag.tryDefragmentFrame(f3) as HeadersFrame; 57 | expect(h.hasEndHeadersFlag, isTrue); 58 | expect(h.hasEndStreamFlag, isFalse); 59 | expect(h.hasPaddedFlag, isFalse); 60 | expect(h.padLength, 0); 61 | expect(h.headerBlockFragment, [1, 2, 3, 4, 5, 6, 7, 8, 9]); 62 | }); 63 | 64 | test('fragmented-push-promise-frame', () { 65 | var defrag = FrameDefragmenter(); 66 | 67 | var f1 = pushPromiseFrame([1, 2, 3], fragmented: true); 68 | var f2 = continuationFrame([4, 5, 6], fragmented: true); 69 | var f3 = continuationFrame([7, 8, 9], fragmented: false); 70 | 71 | expect(defrag.tryDefragmentFrame(f1), isNull); 72 | expect(defrag.tryDefragmentFrame(f2), isNull); 73 | var h = defrag.tryDefragmentFrame(f3) as PushPromiseFrame; 74 | expect(h.hasEndHeadersFlag, isTrue); 75 | expect(h.hasPaddedFlag, isFalse); 76 | expect(h.padLength, 0); 77 | expect(h.headerBlockFragment, [1, 2, 3, 4, 5, 6, 7, 8, 9]); 78 | }); 79 | 80 | test('fragmented-headers-frame--wrong-id', () { 81 | var defrag = FrameDefragmenter(); 82 | 83 | var f1 = headersFrame([1, 2, 3], fragmented: true, streamId: 1); 84 | var f2 = continuationFrame([4, 5, 6], fragmented: true, streamId: 2); 85 | 86 | expect(defrag.tryDefragmentFrame(f1), isNull); 87 | expect( 88 | () => defrag.tryDefragmentFrame(f2), throwsA(isProtocolException)); 89 | }); 90 | 91 | test('fragmented-push-promise-frame', () { 92 | var defrag = FrameDefragmenter(); 93 | 94 | var f1 = pushPromiseFrame([1, 2, 3], fragmented: true, streamId: 1); 95 | var f2 = continuationFrame([4, 5, 6], fragmented: true, streamId: 2); 96 | 97 | expect(defrag.tryDefragmentFrame(f1), isNull); 98 | expect( 99 | () => defrag.tryDefragmentFrame(f2), throwsA(isProtocolException)); 100 | }); 101 | 102 | test('fragmented-headers-frame--no-continuation-frame', () { 103 | var defrag = FrameDefragmenter(); 104 | 105 | var f1 = headersFrame([1, 2, 3], fragmented: true); 106 | var f2 = unknownFrame(); 107 | 108 | expect(defrag.tryDefragmentFrame(f1), isNull); 109 | expect( 110 | () => defrag.tryDefragmentFrame(f2), throwsA(isProtocolException)); 111 | }); 112 | 113 | test('fragmented-push-promise-no-continuation-frame', () { 114 | var defrag = FrameDefragmenter(); 115 | 116 | var f1 = pushPromiseFrame([1, 2, 3], fragmented: true); 117 | var f2 = unknownFrame(); 118 | 119 | expect(defrag.tryDefragmentFrame(f1), isNull); 120 | expect( 121 | () => defrag.tryDefragmentFrame(f2), throwsA(isProtocolException)); 122 | }); 123 | 124 | test('push-without-headres-or-push-promise-frame', () { 125 | var defrag = FrameDefragmenter(); 126 | 127 | var f1 = continuationFrame([4, 5, 6], fragmented: true, streamId: 1); 128 | expect(defrag.tryDefragmentFrame(f1), equals(f1)); 129 | }); 130 | }); 131 | }); 132 | } 133 | -------------------------------------------------------------------------------- /test/src/frames/frame_reader_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:http2/src/frames/frames.dart'; 8 | import 'package:http2/src/settings/settings.dart'; 9 | import 'package:test/test.dart'; 10 | 11 | void main() { 12 | group('frames', () { 13 | group('frame-reader', () { 14 | final maxFrameSize = ActiveSettings().maxFrameSize; 15 | 16 | Stream dataFrame(List body) { 17 | var settings = ActiveSettings(); 18 | var controller = StreamController>(); 19 | var reader = FrameReader(controller.stream, settings); 20 | 21 | // This is a DataFrame: 22 | // - length: n 23 | // - type: [0] 24 | // - flags: [0] 25 | // - stream id: [0, 0, 0, 1] 26 | controller 27 | ..add([0, (body.length >> 8) & 0xff, body.length & 0xff]) 28 | ..add([0]) 29 | ..add([0]) 30 | ..add([0, 0, 0, 1]) 31 | ..add(body) 32 | ..close(); 33 | return reader.startDecoding(); 34 | } 35 | 36 | test('data-frame--max-frame-size', () { 37 | var body = List.filled(maxFrameSize, 0x42); 38 | dataFrame(body).listen(expectAsync1((Frame frame) { 39 | expect(frame, isA()); 40 | expect(frame.header, hasLength(body.length)); 41 | expect(frame.header.flags, 0); 42 | var dataFrame = frame as DataFrame; 43 | expect(dataFrame.hasEndStreamFlag, isFalse); 44 | expect(dataFrame.hasPaddedFlag, isFalse); 45 | expect(dataFrame.bytes, body); 46 | }), 47 | onError: 48 | expectAsync2((Object error, StackTrace stack) {}, count: 0)); 49 | }); 50 | 51 | test('data-frame--max-frame-size-plus-1', () { 52 | var body = List.filled(maxFrameSize + 1, 0x42); 53 | dataFrame(body).listen(expectAsync1((_) {}, count: 0), 54 | onError: expectAsync2((Object error, StackTrace stack) { 55 | expect('$error', contains('Incoming frame is too big')); 56 | })); 57 | }); 58 | 59 | test('incomplete-header', () { 60 | var settings = ActiveSettings(); 61 | 62 | var controller = StreamController>(); 63 | var reader = FrameReader(controller.stream, settings); 64 | 65 | controller 66 | ..add([1]) 67 | ..close(); 68 | 69 | reader.startDecoding().listen(expectAsync1((_) {}, count: 0), 70 | onError: expectAsync2((Object error, StackTrace stack) { 71 | expect('$error', contains('incomplete frame')); 72 | })); 73 | }); 74 | 75 | test('incomplete-frame', () { 76 | var settings = ActiveSettings(); 77 | 78 | var controller = StreamController>(); 79 | var reader = FrameReader(controller.stream, settings); 80 | 81 | // This is a DataFrame: 82 | // - length: [0, 0, 255] 83 | // - type: [0] 84 | // - flags: [0] 85 | // - stream id: [0, 0, 0, 1] 86 | controller 87 | ..add([0, 0, 255, 0, 0, 0, 0, 0, 1]) 88 | ..close(); 89 | 90 | reader.startDecoding().listen(expectAsync1((_) {}, count: 0), 91 | onError: expectAsync2((Object error, StackTrace stack) { 92 | expect('$error', contains('incomplete frame')); 93 | })); 94 | }); 95 | 96 | test('connection-error', () { 97 | var settings = ActiveSettings(); 98 | 99 | var controller = StreamController>(); 100 | var reader = FrameReader(controller.stream, settings); 101 | 102 | controller 103 | ..addError('hello world') 104 | ..close(); 105 | 106 | reader.startDecoding().listen(expectAsync1((_) {}, count: 0), 107 | onError: expectAsync2((Object error, StackTrace stack) { 108 | expect('$error', contains('hello world')); 109 | })); 110 | }); 111 | }); 112 | }); 113 | } 114 | -------------------------------------------------------------------------------- /test/src/frames/frame_writer_reader_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:http2/src/frames/frames.dart'; 8 | import 'package:http2/src/hpack/hpack.dart'; 9 | import 'package:http2/src/settings/settings.dart'; 10 | import 'package:test/test.dart'; 11 | 12 | import '../hpack/hpack_test.dart' show isHeader; 13 | 14 | void main() { 15 | group('frames', () { 16 | group('writer-reader', () { 17 | writerReaderTest('data-frame', 18 | (FrameWriter writer, FrameReader reader, HPackDecoder decoder) async { 19 | writer.writeDataFrame(99, [1, 2, 3], endStream: true); 20 | 21 | var frames = await finishWriting(writer, reader); 22 | expect(frames, hasLength(1)); 23 | expect(frames[0] is DataFrame, isTrue); 24 | 25 | var dataFrame = frames[0] as DataFrame; 26 | expect(dataFrame.header.streamId, 99); 27 | expect(dataFrame.hasPaddedFlag, isFalse); 28 | expect(dataFrame.padLength, 0); 29 | expect(dataFrame.hasEndStreamFlag, isTrue); 30 | expect(dataFrame.bytes, [1, 2, 3]); 31 | }); 32 | 33 | writerReaderTest('headers-frame', 34 | (FrameWriter writer, FrameReader reader, HPackDecoder decoder) async { 35 | writer.writeHeadersFrame(99, [Header.ascii('a', 'b')], endStream: true); 36 | 37 | var frames = await finishWriting(writer, reader); 38 | expect(frames, hasLength(1)); 39 | expect(frames[0] is HeadersFrame, isTrue); 40 | 41 | var headersFrame = frames[0] as HeadersFrame; 42 | expect(headersFrame.header.streamId, 99); 43 | expect(headersFrame.hasPaddedFlag, isFalse); 44 | expect(headersFrame.padLength, 0); 45 | expect(headersFrame.hasEndStreamFlag, isTrue); 46 | expect(headersFrame.hasEndHeadersFlag, isTrue); 47 | expect(headersFrame.exclusiveDependency, false); 48 | expect(headersFrame.hasPriorityFlag, false); 49 | expect(headersFrame.streamDependency, isNull); 50 | expect(headersFrame.weight, isNull); 51 | 52 | var headers = decoder.decode(headersFrame.headerBlockFragment); 53 | expect(headers, hasLength(1)); 54 | expect(headers[0], isHeader('a', 'b')); 55 | }); 56 | 57 | writerReaderTest('priority-frame', 58 | (FrameWriter writer, FrameReader reader, HPackDecoder decoder) async { 59 | writer.writePriorityFrame(99, 44, 33, exclusive: true); 60 | 61 | var frames = await finishWriting(writer, reader); 62 | expect(frames, hasLength(1)); 63 | expect(frames[0] is PriorityFrame, isTrue); 64 | 65 | var priorityFrame = frames[0] as PriorityFrame; 66 | expect(priorityFrame.header.streamId, 99); 67 | expect(priorityFrame.exclusiveDependency, isTrue); 68 | expect(priorityFrame.streamDependency, 44); 69 | expect(priorityFrame.weight, 33); 70 | }); 71 | 72 | writerReaderTest('rst-frame', 73 | (FrameWriter writer, FrameReader reader, HPackDecoder decoder) async { 74 | writer.writeRstStreamFrame(99, 42); 75 | 76 | var frames = await finishWriting(writer, reader); 77 | expect(frames, hasLength(1)); 78 | expect(frames[0] is RstStreamFrame, isTrue); 79 | 80 | var rstFrame = frames[0] as RstStreamFrame; 81 | expect(rstFrame.header.streamId, 99); 82 | expect(rstFrame.errorCode, 42); 83 | }); 84 | 85 | writerReaderTest('settings-frame', 86 | (FrameWriter writer, FrameReader reader, HPackDecoder decoder) async { 87 | writer.writeSettingsFrame([Setting(Setting.SETTINGS_ENABLE_PUSH, 1)]); 88 | 89 | var frames = await finishWriting(writer, reader); 90 | expect(frames, hasLength(1)); 91 | expect(frames[0] is SettingsFrame, isTrue); 92 | 93 | var settingsFrame = frames[0] as SettingsFrame; 94 | expect(settingsFrame.hasAckFlag, false); 95 | expect(settingsFrame.header.streamId, 0); 96 | expect(settingsFrame.settings, hasLength(1)); 97 | expect( 98 | settingsFrame.settings[0].identifier, Setting.SETTINGS_ENABLE_PUSH); 99 | expect(settingsFrame.settings[0].value, 1); 100 | }); 101 | 102 | writerReaderTest('settings-frame-ack', 103 | (FrameWriter writer, FrameReader reader, HPackDecoder decoder) async { 104 | writer.writeSettingsAckFrame(); 105 | 106 | var frames = await finishWriting(writer, reader); 107 | expect(frames, hasLength(1)); 108 | expect(frames[0] is SettingsFrame, isTrue); 109 | 110 | var settingsFrame = frames[0] as SettingsFrame; 111 | expect(settingsFrame.hasAckFlag, true); 112 | expect(settingsFrame.header.streamId, 0); 113 | expect(settingsFrame.settings, hasLength(0)); 114 | }); 115 | 116 | writerReaderTest('push-promise-frame', 117 | (FrameWriter writer, FrameReader reader, HPackDecoder decoder) async { 118 | writer.writePushPromiseFrame(99, 44, [Header.ascii('a', 'b')]); 119 | 120 | var frames = await finishWriting(writer, reader); 121 | expect(frames, hasLength(1)); 122 | expect(frames[0] is PushPromiseFrame, isTrue); 123 | 124 | var pushPromiseFrame = frames[0] as PushPromiseFrame; 125 | expect(pushPromiseFrame.header.streamId, 99); 126 | expect(pushPromiseFrame.hasEndHeadersFlag, isTrue); 127 | expect(pushPromiseFrame.hasPaddedFlag, isFalse); 128 | expect(pushPromiseFrame.padLength, 0); 129 | expect(pushPromiseFrame.promisedStreamId, 44); 130 | 131 | var headers = decoder.decode(pushPromiseFrame.headerBlockFragment); 132 | expect(headers, hasLength(1)); 133 | expect(headers[0], isHeader('a', 'b')); 134 | }); 135 | 136 | writerReaderTest('ping-frame', 137 | (FrameWriter writer, FrameReader reader, HPackDecoder decoder) async { 138 | writer.writePingFrame(44, ack: true); 139 | 140 | var frames = await finishWriting(writer, reader); 141 | expect(frames, hasLength(1)); 142 | expect(frames[0] is PingFrame, isTrue); 143 | 144 | var pingFrame = frames[0] as PingFrame; 145 | expect(pingFrame.header.streamId, 0); 146 | expect(pingFrame.opaqueData, 44); 147 | expect(pingFrame.hasAckFlag, isTrue); 148 | }); 149 | 150 | writerReaderTest('goaway-frame', 151 | (FrameWriter writer, FrameReader reader, HPackDecoder decoder) async { 152 | writer.writeGoawayFrame(44, 33, [1, 2, 3]); 153 | 154 | var frames = await finishWriting(writer, reader); 155 | expect(frames, hasLength(1)); 156 | expect(frames[0] is GoawayFrame, isTrue); 157 | 158 | var goawayFrame = frames[0] as GoawayFrame; 159 | expect(goawayFrame.header.streamId, 0); 160 | expect(goawayFrame.lastStreamId, 44); 161 | expect(goawayFrame.errorCode, 33); 162 | expect(goawayFrame.debugData, [1, 2, 3]); 163 | }); 164 | 165 | writerReaderTest('window-update-frame', 166 | (FrameWriter writer, FrameReader reader, HPackDecoder decoder) async { 167 | writer.writeWindowUpdate(55, streamId: 99); 168 | 169 | var frames = await finishWriting(writer, reader); 170 | expect(frames, hasLength(1)); 171 | expect(frames[0] is WindowUpdateFrame, isTrue); 172 | 173 | var windowUpdateFrame = frames[0] as WindowUpdateFrame; 174 | expect(windowUpdateFrame.header.streamId, 99); 175 | expect(windowUpdateFrame.windowSizeIncrement, 55); 176 | }); 177 | 178 | writerReaderTest('frag-headers-frame', 179 | (FrameWriter writer, FrameReader reader, HPackDecoder decoder) async { 180 | var headerName = [1]; 181 | var headerValue = List.filled(1 << 14, 0x42); 182 | var header = Header(headerName, headerValue); 183 | 184 | writer.writeHeadersFrame(99, [header], endStream: true); 185 | 186 | var frames = await finishWriting(writer, reader); 187 | expect(frames, hasLength(2)); 188 | expect(frames[0] is HeadersFrame, isTrue); 189 | expect(frames[1] is ContinuationFrame, isTrue); 190 | 191 | var headersFrame = frames[0] as HeadersFrame; 192 | expect(headersFrame.header.streamId, 99); 193 | expect(headersFrame.hasPaddedFlag, isFalse); 194 | expect(headersFrame.padLength, 0); 195 | expect(headersFrame.hasEndHeadersFlag, isFalse); 196 | expect(headersFrame.hasEndStreamFlag, isTrue); 197 | expect(headersFrame.hasPriorityFlag, isFalse); 198 | 199 | var contFrame = frames[1] as ContinuationFrame; 200 | expect(contFrame.header.streamId, 99); 201 | expect(contFrame.hasEndHeadersFlag, isTrue); 202 | 203 | var headerBlock = [ 204 | ...headersFrame.headerBlockFragment, 205 | ...contFrame.headerBlockFragment 206 | ]; 207 | 208 | var headers = decoder.decode(headerBlock); 209 | expect(headers, hasLength(1)); 210 | expect(headers[0].name, headerName); 211 | expect(headers[0].value, headerValue); 212 | }); 213 | }); 214 | }); 215 | } 216 | 217 | void writerReaderTest(String name, 218 | Future Function(FrameWriter, FrameReader, HPackDecoder) func) { 219 | test(name, () { 220 | var settings = ActiveSettings(); 221 | var context = HPackContext(); 222 | var controller = StreamController>(); 223 | var writer = FrameWriter(context.encoder, controller, settings); 224 | var reader = FrameReader(controller.stream, settings); 225 | return func(writer, reader, context.decoder); 226 | }); 227 | } 228 | 229 | Future> finishWriting(FrameWriter writer, FrameReader reader) { 230 | return Future.wait([writer.close(), reader.startDecoding().toList()]) 231 | .then((results) => results.last as List); 232 | } 233 | -------------------------------------------------------------------------------- /test/src/frames/frame_writer_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:http2/src/frames/frames.dart'; 8 | import 'package:http2/src/hpack/hpack.dart'; 9 | import 'package:http2/src/settings/settings.dart'; 10 | import 'package:test/test.dart'; 11 | 12 | void main() { 13 | group('frames', () { 14 | group('frame-writer', () { 15 | test('connection-error', () { 16 | var settings = ActiveSettings(); 17 | var context = HPackContext(); 18 | var controller = StreamController>(); 19 | var writer = FrameWriter(context.encoder, controller, settings); 20 | 21 | writer.doneFuture.then(expectAsync1((_) { 22 | // We expect that the writer is done at this point. 23 | })); 24 | 25 | // We cancel here the reading part (simulates a dying socket). 26 | controller.stream.listen((_) {}).cancel(); 27 | }); 28 | }); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /test/src/hpack/huffman_table_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:convert' show ascii; 6 | 7 | import 'package:http2/src/hpack/huffman.dart'; 8 | import 'package:http2/src/hpack/huffman_table.dart'; 9 | import 'package:test/test.dart'; 10 | 11 | void main() { 12 | group('hpack', () { 13 | group('huffman', () { 14 | final decode = http2HuffmanCodec.decode; 15 | final encode = http2HuffmanCodec.encode; 16 | 17 | var hpackSpecTestCases = { 18 | 'www.example.com': [ 19 | 0xf1, 20 | 0xe3, 21 | 0xc2, 22 | 0xe5, 23 | 0xf2, 24 | 0x3a, 25 | 0x6b, 26 | 0xa0, 27 | 0xab, 28 | 0x90, 29 | 0xf4, 30 | 0xff 31 | ], 32 | 'no-cache': [0xa8, 0xeb, 0x10, 0x64, 0x9c, 0xbf], 33 | 'custom-key': [0x25, 0xa8, 0x49, 0xe9, 0x5b, 0xa9, 0x7d, 0x7f], 34 | 'custom-value': [0x25, 0xa8, 0x49, 0xe9, 0x5b, 0xb8, 0xe8, 0xb4, 0xbf], 35 | }; 36 | 37 | test('hpack-spec-testcases', () { 38 | hpackSpecTestCases.forEach((String value, List encoding) { 39 | expect(decode(encoding), ascii.encode(value)); 40 | expect(encode(ascii.encode(value)), encoding); 41 | }); 42 | }); 43 | 44 | test('more-than-7-bit-padding', () { 45 | var data = [ 46 | // Just more-than-7-bitpadding 47 | [0xff], 48 | [0xff, 0xff], 49 | [0xff, 0xff, 0xff], 50 | [0xff, 0xff, 0xff, 0xff], 51 | 52 | // 0xf8 = '&' + more-than-7-bitpadding 53 | [0xf8, 0xff], 54 | [0xf8, 0xff, 0xff], 55 | [0xf8, 0xff, 0xff, 0xff], 56 | [0xf8, 0xff, 0xff, 0xff, 0xff], 57 | 58 | // ')' + entire EOS 59 | [0xfe, 0xff, 0xff, 0xff, 0xff], 60 | ]; 61 | 62 | for (var test in data) { 63 | expect(() => decode(test), throwsA(isHuffmanDecodingException)); 64 | } 65 | }); 66 | 67 | test('incomplete-encoding', () { 68 | var data = [ 69 | // Incomplete encoding 70 | [0xfe], 71 | 72 | // 0xf8 = '&' + Incomplete encoding 73 | [0xf8, 0xfe], 74 | ]; 75 | 76 | for (var test in data) { 77 | expect(() => decode(test), throwsA(isHuffmanDecodingException)); 78 | } 79 | }); 80 | 81 | test('fuzzy-test', () { 82 | var data = [ 83 | [0xb8, 0xa4, 0x4e, 0xe3, 0xb1, 0x4d, 0x3d, 0x63, 0x16, 0x5b, 0x6a], 84 | [0x71, 0x5f, 0xb3, 0xb1, 0x4b, 0x94, 0xe8, 0x2f, 0x4c, 0x3d, 0x04], 85 | [0x95, 0x6d, 0x89, 0xfb, 0x91, 0x6a, 0x6c, 0x52, 0x64, 0x9a, 0xd1], 86 | [0x64, 0x59, 0x79, 0x38, 0xd2, 0x09, 0xea, 0x94, 0x92, 0xda, 0x24], 87 | [0xb0, 0x35, 0xfe, 0xa9, 0x96, 0xb5, 0xe1, 0xde, 0x0a, 0x82, 0x18], 88 | [0x39, 0xe5, 0xdd, 0xba, 0x50, 0xd4, 0x33, 0xa7, 0xb9, 0x63, 0x21], 89 | [0x26, 0x52, 0x7a, 0xaa, 0x52, 0x4d, 0x27, 0x81, 0xe4, 0xef, 0xcd], 90 | [0x17, 0x9e, 0x09, 0xcc, 0xd0, 0x0f, 0x5e, 0x03, 0x45, 0xc9, 0xba], 91 | [0x84, 0xfc, 0x75, 0xeb, 0xcc, 0x9e, 0xb6, 0x50, 0x3f, 0xf8, 0x00], 92 | [0xb9, 0x24, 0x95, 0x13, 0x6d, 0x89, 0xb2, 0x89, 0x86, 0x02, 0xca], 93 | [0xb7, 0xd5, 0x78, 0xfa, 0xa3, 0xa9, 0x90, 0x1b, 0x35, 0xb4, 0x72], 94 | [0x62, 0x9a, 0x31, 0x0c, 0x32, 0x1c, 0x25, 0x2e, 0x1b, 0x56, 0x55], 95 | [0xa9, 0x5d, 0xa8, 0xa4, 0xed, 0x91, 0xeb, 0xba, 0xa0, 0xf9, 0x82], 96 | [0x59, 0x9c, 0xc3, 0x6f, 0x66, 0xec, 0x65, 0xe0, 0x95, 0x6e, 0x34], 97 | [0x3d, 0xc7, 0x0d, 0x6c, 0x01, 0x7d, 0xf2, 0x03, 0x9b, 0xe3, 0xc1], 98 | [0x1d, 0xc6, 0xa4, 0xd1, 0x59, 0x52, 0xce, 0x42, 0x3d, 0xf6, 0xe5], 99 | [0x2d, 0xbd, 0xb6, 0x5c, 0xfb, 0x52, 0x65, 0x2e, 0x7f, 0x03, 0x61], 100 | [0x22, 0x24, 0x50, 0x48, 0x65, 0x5a, 0xe0, 0x0d, 0xf9, 0x78, 0x8d], 101 | [0x72, 0xeb, 0x1d, 0x31, 0xb7, 0xe3, 0xa8, 0x15, 0x1f, 0xf1, 0x43], 102 | [0x45, 0xa4, 0x40, 0x5a, 0x9c, 0x98, 0xa8, 0x6e, 0xac, 0xba, 0x83], 103 | [0x27, 0x55, 0x33, 0xa7, 0x79, 0x08, 0x29, 0x42, 0x6d, 0x89, 0xfc], 104 | [0x3b, 0x65, 0x21, 0x7a, 0x24, 0x58, 0x58, 0x6a, 0x97, 0x6e, 0x7c], 105 | [0x56, 0x41, 0xff, 0x08, 0xaf, 0x9d, 0x33, 0x12, 0xcd, 0xb5, 0x99], 106 | [0x35, 0x48, 0x38, 0x46, 0x3f, 0xee, 0x15, 0x16, 0x8d, 0xf5, 0x16], 107 | [0xcc, 0xc0, 0x1b, 0x1e, 0xf1, 0xae, 0xf7, 0x40, 0xca, 0xc7, 0x9d], 108 | [0x93, 0xae, 0x93, 0xcf, 0x97, 0xdf, 0xba, 0xd6, 0xb2, 0xac, 0x2f], 109 | [0x45, 0xe4, 0x5b, 0x73, 0x54, 0x4c, 0x6c, 0x95, 0xa9, 0xab, 0x7f], 110 | [0x71, 0xac, 0xbf, 0xdf, 0xa4, 0x29, 0xe3, 0x17, 0x3f, 0x24, 0x2f], 111 | [0x5e, 0xc0, 0xf2, 0xbf, 0x5d, 0xc0, 0x31, 0x2d, 0x97, 0x24, 0x1d], 112 | [0x6d, 0x0b, 0x7c, 0x15, 0x68, 0x7c, 0xe1, 0x15, 0xbf, 0x4f, 0x85], 113 | [0x0a, 0x59, 0xf2, 0x3e, 0x48, 0x1d, 0xac, 0xc8, 0x22, 0xb0, 0x37], 114 | [0x3a, 0xe2, 0x9e, 0xec, 0xf9, 0x1e, 0x88, 0xfa, 0xbe, 0x00, 0xee], 115 | [0xc7, 0x5a, 0x1f, 0xc8, 0x48, 0x23, 0x3b, 0x1a, 0x0f, 0xf3, 0x7c], 116 | [0x43, 0x0d, 0x10, 0x03, 0xb2, 0xc6, 0xbd, 0xed, 0x03, 0x19, 0x49], 117 | [0xc9, 0xc4, 0x0e, 0xf3, 0xc6, 0xf4, 0xc1, 0x71, 0xee, 0x96, 0xeb], 118 | [0x18, 0x51, 0x07, 0x36, 0x1a, 0x13, 0x83, 0x69, 0x2b, 0x1b, 0x09], 119 | [0xac, 0x23, 0xb7, 0x47, 0x2d, 0xeb, 0x39, 0xdc, 0x3e, 0xdb, 0x74], 120 | [0x44, 0x60, 0x06, 0x28, 0x5e, 0x8f, 0xef, 0xfc, 0x70, 0x7b, 0x73], 121 | [0xda, 0x38, 0x25, 0x76, 0xa9, 0x1a, 0x99, 0x9a, 0x52, 0xdf, 0x8c], 122 | [0xd4, 0xc4, 0x99, 0x2b, 0x54, 0x88, 0xc9, 0x34, 0x80, 0x43, 0x15], 123 | [0x11, 0xa1, 0xed, 0xe3, 0xb4, 0x88, 0xd5, 0x1d, 0x4a, 0x1b, 0x9f], 124 | [0xfd, 0x2c, 0xb4, 0x6e, 0x65, 0xfb, 0x27, 0x9b, 0x65, 0x55, 0x19], 125 | [0xb6, 0xa4, 0x67, 0x16, 0x8a, 0x59, 0xf5, 0xfc, 0x0f, 0x7e, 0x24], 126 | [0x40, 0x8e, 0x5d, 0x84, 0x90, 0x76, 0x50, 0xdb, 0x72, 0x2a, 0x3b], 127 | [0x7d, 0x1e, 0x9d, 0x2f, 0xad, 0xce, 0x60, 0x00, 0xf8, 0xbc, 0xfa], 128 | [0xc1, 0x2d, 0x32, 0xbd, 0xa2, 0xe7, 0xed, 0x17, 0x48, 0xca, 0xb0], 129 | [0xe6, 0x91, 0x6c, 0xa7, 0xdc, 0x83, 0x58, 0x19, 0x05, 0xb1, 0xa6], 130 | [0xec, 0xb2, 0x16, 0xa3, 0x89, 0x7a, 0xcd, 0x44, 0xe9, 0x3a, 0x98], 131 | [0xcf, 0xef, 0x78, 0x5b, 0x7a, 0xec, 0xa8, 0xfa, 0x6c, 0x78, 0x23], 132 | [0x8b, 0x53, 0x89, 0x82, 0x21, 0x3e, 0xfc, 0xed, 0xe4, 0x6b, 0xa0], 133 | [0xff, 0x28, 0x10, 0xb2, 0x24, 0xf9, 0xb5, 0x3e, 0x08, 0xb2, 0x50], 134 | [0x5e, 0x57, 0x11, 0xff, 0x06, 0x1b, 0xc7, 0x0b, 0x28, 0x5b, 0x34], 135 | [0x00, 0x4a, 0xcc, 0x4e, 0x8e, 0x07, 0xea, 0x93, 0x10, 0x1c, 0x87], 136 | [0xab, 0xc7, 0x7e, 0x10, 0x64, 0x7f, 0xa4, 0x6c, 0xca, 0x93, 0x73], 137 | [0xcf, 0x57, 0xc5, 0x15, 0xbc, 0x47, 0xed, 0x5b, 0x1e, 0xb5, 0x9b], 138 | [0x8e, 0xa5, 0xf3, 0x07, 0xa0, 0x68, 0x1e, 0x9e, 0xea, 0x57, 0x3f], 139 | [0xfe, 0xa7, 0x7f, 0x91, 0xc7, 0xa4, 0x15, 0x7c, 0xa2, 0x00, 0x4c], 140 | [0xb9, 0x62, 0x28, 0xa5, 0x9b, 0x04, 0x98, 0xf9, 0xdd, 0x37, 0x42], 141 | [0xfa, 0x40, 0x1c, 0xce, 0xa0, 0x75, 0x9d, 0xaf, 0xd2, 0x09, 0xae], 142 | [0xa7, 0x8e, 0xdb, 0x1e, 0x8b, 0x94, 0x24, 0x47, 0xd8, 0x04, 0xd7], 143 | [0x69, 0x95, 0x8a, 0x29, 0xbe, 0x9f, 0xfb, 0x71, 0x91, 0x9a, 0x40], 144 | [0x82, 0xed, 0x1e, 0xf5, 0xac, 0x34, 0x17, 0xfe, 0x5f, 0xfd, 0xd3], 145 | [0x81, 0xe6, 0xaa, 0x7b, 0x12, 0xf0, 0xb2, 0xb9, 0x47, 0x02, 0x3c], 146 | [0x05, 0xc3, 0x6d, 0xd5, 0xf1, 0xa4, 0x93, 0xe2, 0x8b, 0x7c, 0xed], 147 | ]; 148 | for (var test in data) { 149 | expect(decode(encode(test)), equals(test)); 150 | } 151 | }); 152 | }); 153 | }); 154 | } 155 | 156 | /// A matcher for HuffmanDecodingExceptions. 157 | const Matcher isHuffmanDecodingException = 158 | TypeMatcher(); 159 | -------------------------------------------------------------------------------- /test/src/ping/ping_handler_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:http2/src/frames/frames.dart'; 8 | import 'package:http2/src/ping/ping_handler.dart'; 9 | import 'package:mockito/mockito.dart'; 10 | import 'package:test/test.dart'; 11 | 12 | import '../error_matchers.dart'; 13 | 14 | void main() { 15 | group('ping-handler', () { 16 | test('successful-ping', () async { 17 | var writer = FrameWriterMock(); 18 | var pingHandler = instantiateHandler(writer); 19 | 20 | var p1 = pingHandler.ping(); 21 | var p2 = pingHandler.ping(); 22 | 23 | verifyInOrder([ 24 | writer.writePingFrame(1), 25 | writer.writePingFrame(2), 26 | ]); 27 | 28 | var header = FrameHeader(8, FrameType.PING, PingFrame.FLAG_ACK, 0); 29 | pingHandler.processPingFrame(PingFrame(header, 1)); 30 | var header2 = FrameHeader(8, FrameType.PING, PingFrame.FLAG_ACK, 0); 31 | pingHandler.processPingFrame(PingFrame(header2, 2)); 32 | 33 | await p1; 34 | await p2; 35 | verifyNoMoreInteractions(writer); 36 | }); 37 | 38 | test('successful-ack-to-remote-ping', () async { 39 | var writer = FrameWriterMock(); 40 | var pingHandler = instantiateHandler(writer); 41 | 42 | var header = FrameHeader(8, FrameType.PING, 0, 0); 43 | pingHandler.processPingFrame(PingFrame(header, 1)); 44 | var header2 = FrameHeader(8, FrameType.PING, 0, 0); 45 | pingHandler.processPingFrame(PingFrame(header2, 2)); 46 | 47 | verifyInOrder([ 48 | writer.writePingFrame(1, ack: true), 49 | writer.writePingFrame(2, ack: true) 50 | ]); 51 | verifyNoMoreInteractions(writer); 52 | }); 53 | 54 | test('ping-unknown-opaque-data', () async { 55 | var writer = FrameWriterMock(); 56 | var pingHandler = instantiateHandler(writer); 57 | 58 | var future = pingHandler.ping(); 59 | verify(writer.writePingFrame(1)).called(1); 60 | 61 | var header = FrameHeader(8, FrameType.PING, PingFrame.FLAG_ACK, 0); 62 | expect(() => pingHandler.processPingFrame(PingFrame(header, 2)), 63 | throwsA(isProtocolException)); 64 | 65 | // Ensure outstanding pings will be completed with an error once we call 66 | // `pingHandler.terminate()`. 67 | unawaited(future.catchError(expectAsync2((Object error, Object _) { 68 | expect(error, 'hello world'); 69 | }))); 70 | pingHandler.terminate('hello world'); 71 | verifyNoMoreInteractions(writer); 72 | }); 73 | 74 | test('terminate-ping-handler', () async { 75 | var writer = FrameWriterMock(); 76 | var pingHandler = instantiateHandler(writer); 77 | 78 | pingHandler.terminate('hello world'); 79 | expect( 80 | () => pingHandler.processPingFrame(PingFrame( 81 | FrameHeader(8, FrameType.PING, PingFrame.FLAG_ACK, 1), 1)), 82 | throwsA(isTerminatedException)); 83 | expect(pingHandler.ping(), throwsA(isTerminatedException)); 84 | verifyZeroInteractions(writer); 85 | }); 86 | 87 | test('ping-non-zero-stream-id', () async { 88 | var writer = FrameWriterMock(); 89 | var pingHandler = instantiateHandler(writer); 90 | 91 | var header = FrameHeader(8, FrameType.PING, PingFrame.FLAG_ACK, 1); 92 | expect(() => pingHandler.processPingFrame(PingFrame(header, 1)), 93 | throwsA(isProtocolException)); 94 | verifyZeroInteractions(writer); 95 | }); 96 | 97 | test('receiving-ping-calls-stream', () async { 98 | final pings = []; 99 | 100 | final writer = FrameWriterMock(); 101 | final pingStream = StreamController()..stream.listen(pings.add); 102 | 103 | PingHandler(writer, pingStream) 104 | ..processPingFrame(PingFrame( 105 | FrameHeader(8, FrameType.PING, 0, 0), 106 | 1, 107 | )) 108 | ..processPingFrame(PingFrame( 109 | FrameHeader(8, FrameType.PING, 0, 0), 110 | 2, 111 | )); 112 | 113 | await pingStream.close(); 114 | 115 | expect(pings, [1, 2]); 116 | }); 117 | }); 118 | } 119 | 120 | PingHandler instantiateHandler(FrameWriterMock writer) { 121 | var controller = StreamController(); 122 | return PingHandler(writer, controller); 123 | } 124 | 125 | class FrameWriterMock extends Mock implements FrameWriter {} 126 | -------------------------------------------------------------------------------- /test/src/settings/settings_handler_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'package:http2/src/frames/frames.dart'; 6 | import 'package:http2/src/hpack/hpack.dart'; 7 | import 'package:http2/src/settings/settings.dart'; 8 | import 'package:mockito/mockito.dart'; 9 | import 'package:test/test.dart'; 10 | 11 | import '../error_matchers.dart'; 12 | 13 | void main() { 14 | group('settings-handler', () { 15 | var pushSettings = [Setting(Setting.SETTINGS_ENABLE_PUSH, 0)]; 16 | var invalidPushSettings = [Setting(Setting.SETTINGS_ENABLE_PUSH, 2)]; 17 | var setMaxTable256 = [Setting(Setting.SETTINGS_HEADER_TABLE_SIZE, 256)]; 18 | 19 | test('successful-setting', () async { 20 | var writer = FrameWriterMock(); 21 | var sh = SettingsHandler( 22 | HPackEncoder(), writer, ActiveSettings(), ActiveSettings()); 23 | 24 | // Start changing settings. 25 | var changed = sh.changeSettings(pushSettings); 26 | verify(writer.writeSettingsFrame(pushSettings)).called(1); 27 | verifyNoMoreInteractions(writer); 28 | 29 | // Check that settings haven't been applied. 30 | expect(sh.acknowledgedSettings.enablePush, true); 31 | 32 | // Simulate remote end to respond with an ACK. 33 | var header = 34 | FrameHeader(0, FrameType.SETTINGS, SettingsFrame.FLAG_ACK, 0); 35 | sh.handleSettingsFrame(SettingsFrame(header, [])); 36 | 37 | await changed; 38 | 39 | // Check that settings have been applied. 40 | expect(sh.acknowledgedSettings.enablePush, false); 41 | }); 42 | 43 | test('ack-remote-settings-change', () { 44 | var writer = FrameWriterMock(); 45 | var sh = SettingsHandler( 46 | HPackEncoder(), writer, ActiveSettings(), ActiveSettings()); 47 | 48 | // Check that settings haven't been applied. 49 | expect(sh.peerSettings.enablePush, true); 50 | 51 | // Simulate remote end by setting the push setting. 52 | var header = FrameHeader(6, FrameType.SETTINGS, 0, 0); 53 | sh.handleSettingsFrame(SettingsFrame(header, pushSettings)); 54 | 55 | // Check that settings have been applied. 56 | expect(sh.peerSettings.enablePush, false); 57 | verify(writer.writeSettingsAckFrame()).called(1); 58 | verifyNoMoreInteractions(writer); 59 | }); 60 | 61 | test('invalid-remote-ack', () { 62 | var writer = FrameWriterMock(); 63 | var sh = SettingsHandler( 64 | HPackEncoder(), writer, ActiveSettings(), ActiveSettings()); 65 | 66 | // Simulates ACK even though we haven't sent any settings. 67 | var header = 68 | FrameHeader(0, FrameType.SETTINGS, SettingsFrame.FLAG_ACK, 0); 69 | var settingsFrame = SettingsFrame(header, const []); 70 | 71 | expect(() => sh.handleSettingsFrame(settingsFrame), 72 | throwsA(isProtocolException)); 73 | verifyZeroInteractions(writer); 74 | }); 75 | 76 | test('invalid-remote-settings-change', () { 77 | var writer = FrameWriterMock(); 78 | var sh = SettingsHandler( 79 | HPackEncoder(), writer, ActiveSettings(), ActiveSettings()); 80 | 81 | // Check that settings haven't been applied. 82 | expect(sh.peerSettings.enablePush, true); 83 | 84 | // Simulate remote end by setting the push setting. 85 | var header = FrameHeader(6, FrameType.SETTINGS, 0, 0); 86 | var settingsFrame = SettingsFrame(header, invalidPushSettings); 87 | expect(() => sh.handleSettingsFrame(settingsFrame), 88 | throwsA(isProtocolException)); 89 | verifyZeroInteractions(writer); 90 | }); 91 | 92 | test('change-max-header-table-size', () { 93 | var writer = FrameWriterMock(); 94 | var mock = HPackEncoderMock(); 95 | var sh = 96 | SettingsHandler(mock, writer, ActiveSettings(), ActiveSettings()); 97 | 98 | // Simulate remote end by setting the push setting. 99 | var header = FrameHeader(6, FrameType.SETTINGS, 0, 0); 100 | var settingsFrame = SettingsFrame(header, setMaxTable256); 101 | sh.handleSettingsFrame(settingsFrame); 102 | verify(mock.updateMaxSendingHeaderTableSize(256)).called(1); 103 | verify(writer.writeSettingsAckFrame()).called(1); 104 | verifyNoMoreInteractions(mock); 105 | verifyNoMoreInteractions(writer); 106 | }); 107 | }); 108 | } 109 | 110 | class FrameWriterMock extends Mock implements FrameWriter {} 111 | 112 | class HPackEncoderMock extends Mock implements HPackEncoder {} 113 | -------------------------------------------------------------------------------- /test/src/streams/helper.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:http2/transport.dart'; 8 | import 'package:test/test.dart'; 9 | 10 | void expectHeadersEqual(List
headers, List
expectedHeaders) { 11 | expect(headers, hasLength(expectedHeaders.length)); 12 | for (var i = 0; i < expectedHeaders.length; i++) { 13 | expect(headers[i].name, expectedHeaders[i].name); 14 | expect(headers[i].value, expectedHeaders[i].value); 15 | } 16 | } 17 | 18 | void expectEmptyStream(Stream s) { 19 | s.listen(expectAsync1((_) {}, count: 0), onDone: expectAsync0(() {})); 20 | } 21 | 22 | void streamTest( 23 | String name, 24 | Future Function(ClientTransportConnection, ServerTransportConnection) 25 | func, 26 | {ClientSettings? settings}) { 27 | return test(name, () { 28 | var bidirect = BidirectionalConnection(); 29 | bidirect.settings = settings; 30 | var client = bidirect.clientConnection; 31 | var server = bidirect.serverConnection; 32 | return func(client, server); 33 | }); 34 | } 35 | 36 | class BidirectionalConnection { 37 | ClientSettings? settings; 38 | final StreamController> writeA = StreamController(); 39 | final StreamController> writeB = StreamController(); 40 | 41 | Stream> get readA => writeA.stream; 42 | 43 | Stream> get readB => writeB.stream; 44 | 45 | ClientTransportConnection get clientConnection => 46 | ClientTransportConnection.viaStreams(readA, writeB, settings: settings); 47 | 48 | ServerTransportConnection get serverConnection => 49 | ServerTransportConnection.viaStreams(readB, writeA); 50 | } 51 | -------------------------------------------------------------------------------- /test/src/streams/simple_flow_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:http2/transport.dart'; 8 | import 'package:test/test.dart'; 9 | 10 | import 'helper.dart'; 11 | 12 | void main() { 13 | group('streams', () { 14 | group('flowcontrol', () { 15 | const numOfOneKB = 1000; 16 | 17 | var expectedHeaders = [Header.ascii('key', 'value')]; 18 | var allBytes = List.generate(numOfOneKB * 1024, (i) => i % 256); 19 | allBytes.addAll(List.generate(42, (i) => 42)); 20 | 21 | void Function(StreamMessage) headersTestFun(String type) { 22 | return expectAsync1((StreamMessage msg) { 23 | expect( 24 | msg, 25 | isA() 26 | .having((m) => m.headers.first.name, 'headers.first.name', 27 | expectedHeaders.first.name) 28 | .having((m) => m.headers.first.value, 'headers.first.value', 29 | expectedHeaders.first.value)); 30 | }); 31 | } 32 | 33 | var serverReceivedAllBytes = Completer(); 34 | 35 | void Function(StreamMessage) messageTestFun(String type) { 36 | var expectHeader = true; 37 | var numBytesReceived = 0; 38 | return (StreamMessage msg) { 39 | if (expectHeader) { 40 | expectHeader = false; 41 | expect( 42 | msg, 43 | isA() 44 | .having((m) => m.headers.first.name, 'headers.first.name', 45 | expectedHeaders.first.name) 46 | .having((m) => m.headers.first.value, 'headers.first.value', 47 | expectedHeaders.first.value)); 48 | } else { 49 | expect(msg, isA()); 50 | var bytes = (msg as DataStreamMessage).bytes; 51 | expect( 52 | bytes, 53 | allBytes.sublist( 54 | numBytesReceived, numBytesReceived + bytes.length)); 55 | numBytesReceived += bytes.length; 56 | 57 | if (numBytesReceived > allBytes.length) { 58 | if (serverReceivedAllBytes.isCompleted) { 59 | throw Exception('Got more messages than expected'); 60 | } 61 | serverReceivedAllBytes.complete(); 62 | } 63 | } 64 | }; 65 | } 66 | 67 | void sendData(TransportStream cStream) { 68 | for (var i = 0; i < (allBytes.length + 1023) ~/ 1024; i++) { 69 | var end = 1024 * (i + 1); 70 | var isLast = end > allBytes.length; 71 | if (isLast) { 72 | end = allBytes.length; 73 | } 74 | cStream.sendData(allBytes.sublist(1024 * i, end), endStream: isLast); 75 | } 76 | } 77 | 78 | streamTest('single-header-request--empty-response', 79 | (ClientTransportConnection client, 80 | ServerTransportConnection server) async { 81 | server.incomingStreams 82 | .listen(expectAsync1((TransportStream sStream) async { 83 | sStream.incomingMessages 84 | .listen(messageTestFun('server'), onDone: expectAsync0(() {})); 85 | sStream.sendHeaders(expectedHeaders, endStream: true); 86 | await serverReceivedAllBytes.future; 87 | })); 88 | 89 | TransportStream cStream = client.makeRequest(expectedHeaders); 90 | sendData(cStream); 91 | cStream.incomingMessages 92 | .listen(headersTestFun('client'), onDone: expectAsync0(() {})); 93 | }); 94 | }); 95 | }); 96 | } 97 | -------------------------------------------------------------------------------- /test/src/streams/simple_push_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | import 'dart:convert' show utf8; 7 | 8 | import 'package:http2/transport.dart'; 9 | import 'package:test/test.dart'; 10 | 11 | import 'helper.dart'; 12 | 13 | void main() { 14 | group('streams', () { 15 | group('server-push', () { 16 | const numOfOneKB = 1000; 17 | 18 | var expectedHeaders = [Header.ascii('key', 'value')]; 19 | var allBytes = List.generate(numOfOneKB * 1024, (i) => i % 256); 20 | allBytes.addAll(List.generate(42, (i) => 42)); 21 | 22 | void testHeaders(List
headers) { 23 | expect(headers, hasLength(expectedHeaders.length)); 24 | for (var i = 0; i < headers.length; i++) { 25 | expect(headers[i].name, expectedHeaders[i].name); 26 | expect(headers[i].value, expectedHeaders[i].value); 27 | } 28 | } 29 | 30 | void Function(StreamMessage) headersTestFun() { 31 | return expectAsync1((StreamMessage msg) { 32 | expect(msg, isA()); 33 | testHeaders((msg as HeadersStreamMessage).headers); 34 | }); 35 | } 36 | 37 | var serverReceivedAllBytes = Completer(); 38 | 39 | Future readData(StreamIterator iterator) async { 40 | var all = []; 41 | 42 | while (await iterator.moveNext()) { 43 | var msg = iterator.current; 44 | expect(msg, isA()); 45 | all.addAll((msg as DataStreamMessage).bytes); 46 | } 47 | 48 | return utf8.decode(all); 49 | } 50 | 51 | Future sendData(TransportStream stream, String data) { 52 | stream.outgoingMessages 53 | ..add(DataStreamMessage(utf8.encode(data))) 54 | ..close(); 55 | return stream.outgoingMessages.done; 56 | } 57 | 58 | streamTest('server-push', (ClientTransportConnection client, 59 | ServerTransportConnection server) async { 60 | server.incomingStreams 61 | .listen(expectAsync1((ServerTransportStream sStream) async { 62 | var pushStream = sStream.push(expectedHeaders); 63 | pushStream.sendHeaders(expectedHeaders); 64 | await sendData(pushStream, 'pushing "hello world" :)'); 65 | 66 | unawaited(sStream.incomingMessages.drain()); 67 | sStream.sendHeaders(expectedHeaders, endStream: true); 68 | 69 | await serverReceivedAllBytes.future; 70 | })); 71 | 72 | var cStream = client.makeRequest(expectedHeaders, endStream: true); 73 | cStream.incomingMessages 74 | .listen(headersTestFun(), onDone: expectAsync0(() {})); 75 | cStream.peerPushes 76 | .listen(expectAsync1((TransportStreamPush push) async { 77 | testHeaders(push.requestHeaders); 78 | 79 | var iterator = StreamIterator(push.stream.incomingMessages); 80 | var hasNext = await iterator.moveNext(); 81 | expect(hasNext, isTrue); 82 | testHeaders((iterator.current as HeadersStreamMessage).headers); 83 | 84 | var msg = await readData(iterator); 85 | expect(msg, 'pushing "hello world" :)'); 86 | })); 87 | }, settings: const ClientSettings(allowServerPushes: true)); 88 | }); 89 | }); 90 | } 91 | -------------------------------------------------------------------------------- /test/src/streams/streams_test.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file 2 | // for details. All rights reserved. Use of this source code is governed by a 3 | // BSD-style license that can be found in the LICENSE file. 4 | 5 | import 'dart:async'; 6 | 7 | import 'package:http2/transport.dart'; 8 | import 'package:test/test.dart'; 9 | 10 | import 'helper.dart'; 11 | 12 | void main() { 13 | group('streams', () { 14 | streamTest('single-header-request--empty-response', 15 | (ClientTransportConnection client, 16 | ServerTransportConnection server) async { 17 | var expectedHeaders = [Header.ascii('key', 'value')]; 18 | 19 | server.incomingStreams.listen(expectAsync1((TransportStream sStream) { 20 | sStream.incomingMessages.listen(expectAsync1((StreamMessage msg) { 21 | expect(msg, isA()); 22 | 23 | var headersMsg = msg as HeadersStreamMessage; 24 | expectHeadersEqual(headersMsg.headers, expectedHeaders); 25 | }), onDone: expectAsync0(() {})); 26 | sStream.outgoingMessages.close(); 27 | })); 28 | 29 | TransportStream cStream = 30 | client.makeRequest(expectedHeaders, endStream: true); 31 | expectEmptyStream(cStream.incomingMessages); 32 | }); 33 | 34 | streamTest('multi-header-request--empty-response', 35 | (ClientTransportConnection client, 36 | ServerTransportConnection server) async { 37 | var expectedHeaders = [Header.ascii('key', 'value')]; 38 | 39 | server.incomingStreams.listen(expectAsync1((TransportStream sStream) { 40 | sStream.incomingMessages.listen( 41 | expectAsync1((StreamMessage msg) { 42 | expect(msg, isA()); 43 | 44 | var headersMsg = msg as HeadersStreamMessage; 45 | expectHeadersEqual(headersMsg.headers, expectedHeaders); 46 | }, count: 3), 47 | onDone: expectAsync0(() {})); 48 | sStream.outgoingMessages.close(); 49 | })); 50 | 51 | TransportStream cStream = client.makeRequest(expectedHeaders); 52 | cStream.sendHeaders(expectedHeaders); 53 | cStream.sendHeaders(expectedHeaders, endStream: true); 54 | expectEmptyStream(cStream.incomingMessages); 55 | }); 56 | 57 | streamTest('multi-data-request--empty-response', 58 | (ClientTransportConnection client, 59 | ServerTransportConnection server) async { 60 | var expectedHeaders = [Header.ascii('key', 'value')]; 61 | var chunks = [ 62 | [1], 63 | [2], 64 | [3] 65 | ]; 66 | 67 | server.incomingStreams 68 | .listen(expectAsync1((TransportStream sStream) async { 69 | var isFirst = true; 70 | var receivedChunks = >[]; 71 | sStream.incomingMessages.listen( 72 | expectAsync1((StreamMessage msg) { 73 | if (isFirst) { 74 | isFirst = false; 75 | expect(msg, isA()); 76 | 77 | var headersMsg = msg as HeadersStreamMessage; 78 | expectHeadersEqual(headersMsg.headers, expectedHeaders); 79 | } else { 80 | expect(msg, isA()); 81 | 82 | var dataMsg = msg as DataStreamMessage; 83 | receivedChunks.add(dataMsg.bytes); 84 | } 85 | }, count: 1 + chunks.length), onDone: expectAsync0(() { 86 | expect(receivedChunks, chunks); 87 | })); 88 | unawaited(sStream.outgoingMessages.close()); 89 | })); 90 | 91 | TransportStream cStream = client.makeRequest(expectedHeaders); 92 | chunks.forEach(cStream.sendData); 93 | unawaited(cStream.outgoingMessages.close()); 94 | expectEmptyStream(cStream.incomingMessages); 95 | }); 96 | 97 | streamTest('single-header-request--single-headers-response', 98 | (ClientTransportConnection client, 99 | ServerTransportConnection server) async { 100 | var expectedHeaders = [Header.ascii('key', 'value')]; 101 | 102 | server.incomingStreams.listen(expectAsync1((TransportStream sStream) { 103 | sStream.incomingMessages.listen(expectAsync1((StreamMessage msg) { 104 | expect(msg, isA()); 105 | 106 | var headersMsg = msg as HeadersStreamMessage; 107 | expectHeadersEqual(headersMsg.headers, expectedHeaders); 108 | }), onDone: expectAsync0(() {})); 109 | sStream.sendHeaders(expectedHeaders, endStream: true); 110 | })); 111 | 112 | TransportStream cStream = 113 | client.makeRequest(expectedHeaders, endStream: true); 114 | 115 | cStream.incomingMessages.listen(expectAsync1((StreamMessage msg) { 116 | expect(msg, isA()); 117 | 118 | var headersMsg = msg as HeadersStreamMessage; 119 | expectHeadersEqual(headersMsg.headers, expectedHeaders); 120 | }), onDone: expectAsync0(() {})); 121 | }); 122 | 123 | streamTest('single-header-request--multi-headers-response', 124 | (ClientTransportConnection client, 125 | ServerTransportConnection server) async { 126 | var expectedHeaders = [Header.ascii('key', 'value')]; 127 | 128 | server.incomingStreams.listen(expectAsync1((TransportStream sStream) { 129 | sStream.incomingMessages.listen(expectAsync1((StreamMessage msg) { 130 | expect(msg, isA()); 131 | 132 | var headersMsg = msg as HeadersStreamMessage; 133 | expectHeadersEqual(headersMsg.headers, expectedHeaders); 134 | }), onDone: expectAsync0(() {})); 135 | 136 | sStream.sendHeaders(expectedHeaders); 137 | sStream.sendHeaders(expectedHeaders); 138 | sStream.sendHeaders(expectedHeaders, endStream: true); 139 | })); 140 | 141 | TransportStream cStream = 142 | client.makeRequest(expectedHeaders, endStream: true); 143 | 144 | cStream.incomingMessages.listen(expectAsync1((StreamMessage msg) { 145 | expect(msg, isA()); 146 | 147 | var headersMsg = msg as HeadersStreamMessage; 148 | expectHeadersEqual(headersMsg.headers, expectedHeaders); 149 | }, count: 3)); 150 | }); 151 | 152 | streamTest('single-header-request--multi-data-response', 153 | (ClientTransportConnection client, 154 | ServerTransportConnection server) async { 155 | var expectedHeaders = [Header.ascii('key', 'value')]; 156 | var chunks = [ 157 | [1], 158 | [2], 159 | [3] 160 | ]; 161 | 162 | server.incomingStreams.listen(expectAsync1((TransportStream sStream) { 163 | sStream.incomingMessages.listen(expectAsync1((StreamMessage msg) { 164 | expect(msg, isA()); 165 | 166 | var headersMsg = msg as HeadersStreamMessage; 167 | expectHeadersEqual(headersMsg.headers, expectedHeaders); 168 | }), onDone: expectAsync0(() {})); 169 | 170 | chunks.forEach(sStream.sendData); 171 | sStream.outgoingMessages.close(); 172 | })); 173 | 174 | TransportStream cStream = client.makeRequest(expectedHeaders); 175 | unawaited(cStream.outgoingMessages.close()); 176 | 177 | var i = 0; 178 | cStream.incomingMessages.listen(expectAsync1((StreamMessage msg) { 179 | expect( 180 | msg, 181 | isA() 182 | .having((m) => m.bytes, 'bytes', chunks[i++])); 183 | }, count: chunks.length)); 184 | }); 185 | }); 186 | 187 | streamTest('single-data-request--data-trailer-response', 188 | (ClientTransportConnection client, 189 | ServerTransportConnection server) async { 190 | var expectedHeaders = [Header.ascii('key', 'value')]; 191 | var chunk = [1]; 192 | 193 | server.incomingStreams.listen(expectAsync1((TransportStream sStream) async { 194 | var isFirst = true; 195 | List? receivedChunk; 196 | sStream.incomingMessages.listen( 197 | expectAsync1((StreamMessage msg) { 198 | if (isFirst) { 199 | isFirst = false; 200 | expect(msg, isA()); 201 | expect(msg.endStream, false); 202 | 203 | var headersMsg = msg as HeadersStreamMessage; 204 | expectHeadersEqual(headersMsg.headers, expectedHeaders); 205 | } else { 206 | expect(msg, isA()); 207 | expect(msg.endStream, true); 208 | expect(receivedChunk, null); 209 | 210 | var dataMsg = msg as DataStreamMessage; 211 | receivedChunk = dataMsg.bytes; 212 | } 213 | }, count: 2), onDone: expectAsync0(() { 214 | expect(receivedChunk, chunk); 215 | sStream.sendData([2]); 216 | sStream.sendHeaders(expectedHeaders, endStream: true); 217 | })); 218 | })); 219 | 220 | TransportStream cStream = client.makeRequest(expectedHeaders); 221 | cStream.sendData(chunk, endStream: true); 222 | 223 | var isFirst = true; 224 | cStream.incomingMessages.listen(expectAsync1((StreamMessage msg) { 225 | if (isFirst) { 226 | expect(msg, const TypeMatcher()); 227 | final data = msg as DataStreamMessage; 228 | expect(data.bytes, [2]); 229 | isFirst = false; 230 | } else { 231 | expect(msg, const TypeMatcher()); 232 | final trailer = msg as HeadersStreamMessage; 233 | expect(trailer.endStream, true); 234 | expectHeadersEqual(trailer.headers, expectedHeaders); 235 | } 236 | }, count: 2)); 237 | }); 238 | } 239 | --------------------------------------------------------------------------------