[roll] Update third-party dart packages
Updated:
Change-Id: I2b875e2128e808e14357321b2c1bed8236c93072
diff --git a/_discoveryapis_commons/.gitignore b/_discoveryapis_commons/.gitignore
index d69d97c..813c59a 100644
--- a/_discoveryapis_commons/.gitignore
+++ b/_discoveryapis_commons/.gitignore
@@ -1,11 +1,3 @@
-.buildlog
-.DS_Store
-.idea
.packages
-.project
-.pub/
.dart_tool/
-.settings/
-build
-packages
pubspec.lock
diff --git a/_discoveryapis_commons/.status b/_discoveryapis_commons/.status
deleted file mode 100644
index 364ca4b..0000000
--- a/_discoveryapis_commons/.status
+++ /dev/null
@@ -1,4 +0,0 @@
-# Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
-# for details. All rights reserved. Use of this source code is governed by a
-# BSD-style license that can be found in the LICENSE file.
-
diff --git a/_discoveryapis_commons/.travis.yml b/_discoveryapis_commons/.travis.yml
index dece76c..a26ab6a 100644
--- a/_discoveryapis_commons/.travis.yml
+++ b/_discoveryapis_commons/.travis.yml
@@ -1,19 +1,23 @@
language: dart
dart:
- - dev
- - stable
+- dev
+- 2.0.0
dart_task:
- - test
- - test: --platform firefox -j 1
- - dartanalyzer: --fatal-infos --fatal-warnings .
- - dartfmt
+- test
+- test: --platform firefox -j 1
+- dartanalyzer: --fatal-infos --fatal-warnings .
+
+matrix:
+ include:
+ - dart: dev
+ dart_task: dartfmt
# Only building master means that we don't run two builds for each pull request.
branches:
only: [master]
cache:
- directories:
- - $HOME/.pub-cache
+ directories:
+ - $HOME/.pub-cache
diff --git a/_discoveryapis_commons/BUILD.gn b/_discoveryapis_commons/BUILD.gn
index 2255734..45eca6b 100644
--- a/_discoveryapis_commons/BUILD.gn
+++ b/_discoveryapis_commons/BUILD.gn
@@ -1,4 +1,4 @@
-# This file is generated by importer.py for _discoveryapis_commons-0.1.8+1
+# This file is generated by importer.py for _discoveryapis_commons-0.1.9
import("//build/dart/dart_library.gni")
diff --git a/_discoveryapis_commons/CHANGELOG.md b/_discoveryapis_commons/CHANGELOG.md
index 30b87eb..8397d80 100644
--- a/_discoveryapis_commons/CHANGELOG.md
+++ b/_discoveryapis_commons/CHANGELOG.md
@@ -1,3 +1,11 @@
+## 0.1.9
+
+ - Added a `x-goog-api-client` header for client library identification.
+
+## 0.1.8+2
+
+- Filter out headers we're not allowed to send when operating in a browser.
+
## 0.1.8+1
- Fix `content-length` bug introduced in `0.1.8`.
diff --git a/_discoveryapis_commons/analysis_options.yaml b/_discoveryapis_commons/analysis_options.yaml
index 4ef8ee7..958f92a 100644
--- a/_discoveryapis_commons/analysis_options.yaml
+++ b/_discoveryapis_commons/analysis_options.yaml
@@ -11,22 +11,20 @@
linter:
rules:
- always_declare_return_types
- #- annotate_overrides
- avoid_empty_else
- avoid_function_literals_in_foreach_calls
- avoid_init_to_null
- avoid_null_checks_in_equality_operators
+ - avoid_relative_lib_imports
- avoid_renaming_method_parameters
- avoid_return_types_on_setters
- avoid_returning_null
+ - avoid_shadowing_type_parameters
- avoid_types_as_parameter_names
- avoid_unused_constructor_parameters
- await_only_futures
- camel_case_types
- cancel_subscriptions
- #- cascade_invocations
- #- comment_references
- #- constant_identifier_names
- control_flow_in_finally
- directives_ordering
- empty_catches
@@ -36,17 +34,15 @@
- implementation_imports
- invariant_booleans
- iterable_contains_unrelated_type
- #- library_names
- library_prefixes
- list_remove_unrelated_type
- literal_only_boolean_expressions
- no_adjacent_strings_in_list
- no_duplicate_case_values
- #- non_constant_identifier_names
+ - null_closures
- omit_local_variable_types
- only_throw_errors
- overridden_fields
- #- package_api_docs
- package_names
- package_prefixed_library_names
- prefer_adjacent_string_concatenation
@@ -56,22 +52,23 @@
- prefer_contains
- prefer_equal_for_default_values
- prefer_final_fields
+ - prefer_generic_function_type_aliases
- prefer_initializing_formals
- #- prefer_interpolation_to_compose_strings
- prefer_is_empty
- prefer_is_not_empty
- prefer_single_quotes
- prefer_typing_uninitialized_variables
- recursive_getters
- #- slash_for_doc_comments
- - super_goes_last
+ - slash_for_doc_comments
- test_types_in_equals
- throw_in_finally
- type_init_formals
- unawaited_futures
- unnecessary_brace_in_string_interps
+ - unnecessary_const
- unnecessary_getters_setters
- unnecessary_lambdas
+ - unnecessary_new
- unnecessary_null_aware_assignments
- unnecessary_statements
- unnecessary_this
diff --git a/_discoveryapis_commons/codereview.settings b/_discoveryapis_commons/codereview.settings
deleted file mode 100644
index 5272cec..0000000
--- a/_discoveryapis_commons/codereview.settings
+++ /dev/null
@@ -1,3 +0,0 @@
-CODE_REVIEW_SERVER: https://codereview.chromium.org
-VIEW_VC: https://github.com/dart-lang/discoveryapis_commons/commit/
-CC_LIST: reviews@dartlang.org
diff --git a/_discoveryapis_commons/lib/src/clients.dart b/_discoveryapis_commons/lib/src/clients.dart
index 66cb257..42210c6 100644
--- a/_discoveryapis_commons/lib/src/clients.dart
+++ b/_discoveryapis_commons/lib/src/clients.dart
@@ -10,13 +10,20 @@
import 'package:http/http.dart' as http;
import 'requests.dart' as client_requests;
+import 'version_fallback.dart' if (dart.library.io) 'version_io.dart';
const CONTENT_TYPE_JSON_UTF8 = 'application/json; charset=utf-8';
-/**
- * Base class for all API clients, offering generic methods for
- * HTTP Requests to the API
- */
+/// List of headers that is forbidden in current execution context.
+///
+/// In a browser context we're not allowed to set `user-agent` and
+/// `content-length` headers.
+const _forbiddenHeaders = bool.fromEnvironment('dart.library.html')
+ ? <String>['user-agent', 'content-length']
+ : <String>[];
+
+/// Base class for all API clients, offering generic methods for
+/// HTTP Requests to the API
class ApiRequester {
final http.Client _httpClient;
final String _rootUrl;
@@ -28,22 +35,20 @@
assert(_rootUrl.endsWith('/'));
}
- /**
- * Sends a HTTPRequest using [method] (usually GET or POST) to [requestUrl]
- * using the specified [urlParams] and [queryParams]. Optionally include a
- * [body] and/or [uploadMedia] in the request.
- *
- * If [uploadMedia] was specified [downloadOptions] must be
- * [DownloadOptions.Metadata] or `null`.
- *
- * If [downloadOptions] is [DownloadOptions.Metadata] the result will be
- * decoded as JSON.
- *
- * If [downloadOptions] is `null` the result will be a Future completing with
- * `null`.
- *
- * Otherwise the result will be downloaded as a [client_requests.Media]
- */
+ /// Sends a HTTPRequest using [method] (usually GET or POST) to [requestUrl]
+ /// using the specified [urlParams] and [queryParams]. Optionally include a
+ /// [body] and/or [uploadMedia] in the request.
+ ///
+ /// If [uploadMedia] was specified [downloadOptions] must be
+ /// [DownloadOptions.Metadata] or `null`.
+ ///
+ /// If [downloadOptions] is [DownloadOptions.Metadata] the result will be
+ /// decoded as JSON.
+ ///
+ /// If [downloadOptions] is `null` the result will be a Future completing with
+ /// `null`.
+ ///
+ /// Otherwise the result will be downloaded as a [client_requests.Media]
Future request(String requestUrl, String method,
{String body,
Map<String, List<String>> queryParams,
@@ -202,14 +207,20 @@
'content-type': CONTENT_TYPE_JSON_UTF8,
'content-length': '$length',
'range': 'bytes=${downloadRange.start}-${downloadRange.end}',
+ 'x-goog-api-client': 'gl-dart/$dartVersion',
};
} else {
headers = {
'user-agent': _userAgent,
'content-type': CONTENT_TYPE_JSON_UTF8,
'content-length': '$length',
+ 'x-goog-api-client': 'gl-dart/$dartVersion',
};
}
+ // Filter out headers forbidden in the browser (in calling in browser).
+ // If we don't do this, the browser will complain that we're attempting
+ // to set a header that we're not allowed to set.
+ headers.removeWhere((key, value) => _forbiddenHeaders.contains(key));
var request = _RequestImpl(method, uri, bodyController.stream);
request.headers.addAll(headers);
@@ -218,7 +229,7 @@
if (uploadMedia != null) {
// Three upload types:
- // 1. Resumable: Upload of data + metdata with multiple requests.
+ // 1. Resumable: Upload of data + metadata with multiple requests.
// 2. Simple: Upload of media.
// 3. Multipart: Upload of data + metadata.
@@ -246,9 +257,7 @@
}
}
-/**
- * Does media uploads using the multipart upload protocol.
- */
+/// Does media uploads using the multipart upload protocol.
class MultipartMediaUploader {
static final _boundary = '314159265358979323846';
static final _base64Encoder = Base64Encoder();
@@ -273,11 +282,11 @@
// This guarantees us that [_body] cannot contain a valid multipart
// boundary.
var bodyHead = '--$_boundary\r\n'
- 'Content-Type: $CONTENT_TYPE_JSON_UTF8\r\n\r\n' +
+ 'Content-Type: $CONTENT_TYPE_JSON_UTF8\r\n\r\n' +
_body +
'\r\n--$_boundary\r\n'
- 'Content-Type: ${_uploadMedia.contentType}\r\n'
- 'Content-Transfer-Encoding: base64\r\n\r\n';
+ 'Content-Type: ${_uploadMedia.contentType}\r\n'
+ 'Content-Transfer-Encoding: base64\r\n\r\n';
var bodyTail = '\r\n--$_boundary--';
var totalLength =
@@ -305,9 +314,7 @@
}
}
-/**
- * Base64 encodes a stream of bytes.
- */
+/// Base64 encodes a stream of bytes.
class Base64Encoder extends StreamTransformerBase<List<int>, String> {
static int lengthOfBase64Stream(int lengthOfByteStream) {
return ((lengthOfByteStream + 2) ~/ 3) * 4;
@@ -347,7 +354,7 @@
// Convert & Send main bytes.
if (start == 0 && end == bytes.length) {
- // Fast path if [bytes] are devisible by 3.
+ // Fast path if [bytes] are divisible by 3.
controller.add(base64.encode(bytes));
} else {
controller.add(base64.encode(bytes.sublist(start, end)));
@@ -386,9 +393,7 @@
}
// TODO: Buffer less if we know the content length in advance.
-/**
- * Does media uploads using the resumable upload protocol.
- */
+/// Does media uploads using the resumable upload protocol.
class ResumableMediaUploader {
final http.Client _httpClient;
final client_requests.Media _uploadMedia;
@@ -401,12 +406,10 @@
ResumableMediaUploader(this._httpClient, this._uploadMedia, this._body,
this._uri, this._method, this._options, this._userAgent);
- /**
- * Returns the final [http.StreamedResponse] if the upload succeded and
- * completes with an error otherwise.
- *
- * The returned response stream has not been listened to.
- */
+ /// Returns the final [http.StreamedResponse] if the upload succeed and
+ /// completes with an error otherwise.
+ ///
+ /// The returned response stream has not been listened to.
Future<http.StreamedResponse> upload() {
return _startSession().then((Uri uploadUri) {
StreamSubscription subscription;
@@ -494,11 +497,9 @@
});
}
- /**
- * Starts a resumable upload.
- *
- * Returns the [Uri] which should be used for uploading all content.
- */
+ /// Starts a resumable upload.
+ ///
+ /// Returns the [Uri] which should be used for uploading all content.
Future<Uri> _startSession() {
var length = 0;
List<int> bytes;
@@ -530,19 +531,15 @@
});
}
- /**
- * Uploads [chunk], retries upon server errors. The response stream will be
- * drained.
- */
+ /// Uploads [chunk], retries upon server errors. The response stream will be
+ /// drained.
Future _uploadChunkDrained(Uri uri, ResumableChunk chunk) {
return _uploadChunkResumable(uri, chunk).then((response) {
return response.stream.drain();
});
}
- /**
- * Does repeated attempts to upload [chunk].
- */
+ /// Does repeated attempts to upload [chunk].
Future<http.StreamedResponse> _uploadChunkResumable(
Uri uri, ResumableChunk chunk,
{bool lastChunk = false}) {
@@ -554,8 +551,8 @@
(status == 500 || (502 <= status && status < 504))) {
return response.stream.drain().then((_) {
// Delay the next attempt. Default backoff function is exponential.
- var failedAttemts = _options.numberOfAttempts - attemptsLeft;
- Duration duration = _options.backoffFunction(failedAttemts);
+ var failedAttempts = _options.numberOfAttempts - attemptsLeft;
+ var duration = _options.backoffFunction(failedAttempts) as Duration;
if (duration == null) {
throw client_requests.DetailedApiRequestError(
status,
@@ -590,15 +587,13 @@
return tryUpload(_options.numberOfAttempts - 1);
}
- /**
- * Uploads [length] bytes in [byteArrays] and ensures the upload was
- * successful.
- *
- * Content-Range: [start ... (start + length)[
- *
- * Returns the returned [http.StreamedResponse] or completes with an error if
- * the upload did not succeed. The response stream will not be listened to.
- */
+ /// Uploads [length] bytes in [byteArrays] and ensures the upload was
+ /// successful.
+ ///
+ /// Content-Range: [start ... (start + length)[
+ ///
+ /// Returns the returned [http.StreamedResponse] or completes with an error if
+ /// the upload did not succeed. The response stream will not be listened to.
Future<http.StreamedResponse> _uploadChunk(Uri uri, ResumableChunk chunk,
{bool lastChunk = false}) {
// If [uploadMedia.length] is null, we do not know the length.
@@ -645,9 +640,7 @@
}
}
-/**
- * Represents a stack of [ResumableChunk]s.
- */
+/// Represents a stack of [ResumableChunk]s.
class ChunkStack {
final int _chunkSize;
final List<ResumableChunk> _chunkStack = [];
@@ -661,21 +654,15 @@
ChunkStack(this._chunkSize);
- /**
- * Whether data for a not-yet-finished [ResumableChunk] is present. A call to
- * `finalize` will create a [ResumableChunk] of this data.
- */
+ /// Whether data for a not-yet-finished [ResumableChunk] is present. A call to
+ /// `finalize` will create a [ResumableChunk] of this data.
bool get hasPartialChunk => _length > 0;
- /**
- * The number of chunks in this [ChunkStack].
- */
+ /// The number of chunks in this [ChunkStack].
int get length => _chunkStack.length;
- /**
- * The total number of bytes which have been converted to [ResumableChunk]s.
- * Can only be called once this [ChunkStack] has been finalized.
- */
+ /// The total number of bytes which have been converted to [ResumableChunk]s.
+ /// Can only be called once this [ChunkStack] has been finalized.
int get totalByteLength {
if (!_finalized) {
throw StateError('ChunkStack has not been finalized yet.');
@@ -684,19 +671,15 @@
return _offset;
}
- /**
- * Returns the chunks [from] ... [to] and deletes it from the stack.
- */
+ /// Returns the chunks [from] ... [to] and deletes it from the stack.
List<ResumableChunk> removeSublist(int from, int to) {
var sublist = _chunkStack.sublist(from, to);
_chunkStack.removeRange(from, to);
return sublist;
}
- /**
- * Adds [bytes] to the buffer. If the buffer is larger than the given chunk
- * size a new [ResumableChunk] will be created.
- */
+ /// Adds [bytes] to the buffer. If the buffer is larger than the given chunk
+ /// size a new [ResumableChunk] will be created.
void addBytes(List<int> bytes) {
if (_finalized) {
throw StateError('ChunkStack has already been finalized.');
@@ -724,10 +707,8 @@
}
}
- /**
- * Finalizes this [ChunkStack] and creates the last chunk (may have less bytes
- * than the chunk size, but not zero).
- */
+ /// Finalizes this [ChunkStack] and creates the last chunk (may have less bytes
+ /// than the chunk size, but not zero).
void finalize() {
if (_finalized) {
throw StateError('ChunkStack has already been finalized.');
@@ -741,17 +722,13 @@
}
}
-/**
- * Represents a chunk of data that will be transferred in one http request.
- */
+/// Represents a chunk of data that will be transferred in one http request.
class ResumableChunk {
final List<List<int>> byteArrays;
final int offset;
final int length;
- /**
- * Index of the next byte after this chunk.
- */
+ /// Index of the next byte after this chunk.
int get endOfChunk => offset + length;
ResumableChunk(this.byteArrays, this.offset, this.length);
@@ -802,7 +779,7 @@
// allowed characters being those in the set
// (unreserved / reserved / pct-encoded)
- // NOTE: The chracters [ and ] need (according to URI Template spec) not be
+ // NOTE: The characters [ and ] need (according to URI Template spec) not be
// percent encoded. The dart implementation does percent-encode [ and ].
// This gives us in effect a conservative encoding, since the server side
// must interpret percent-encoded parts anyway due to arbitrary unicode.
@@ -858,7 +835,7 @@
if (stringStream != null) {
var jsonResponse = await stringStream.transform(json.decoder).first;
if (jsonResponse is Map && jsonResponse['error'] is Map) {
- final Map error = jsonResponse['error'];
+ final error = jsonResponse['error'] as Map;
final codeValue = error['code'];
final message = error['message'] as String;
diff --git a/_discoveryapis_commons/lib/src/requests.dart b/_discoveryapis_commons/lib/src/requests.dart
index 3443c1b..ff53d9f 100644
--- a/_discoveryapis_commons/lib/src/requests.dart
+++ b/_discoveryapis_commons/lib/src/requests.dart
@@ -7,22 +7,18 @@
import 'dart:async' as async;
import 'dart:core' as core;
-/**
- * Represents a media consisting of a stream of bytes, a content type and a
- * length.
- */
+/// Represents a media consisting of a stream of bytes, a content type and a
+/// length.
class Media {
final async.Stream<core.List<core.int>> stream;
final core.String contentType;
final core.int length;
- /**
- * Creates a new [Media] with a byte [stream] of length [length] with a
- * [contentType].
- *
- * When uploading media, [length] can only be null if [ResumableUploadOptions]
- * is used.
- */
+ /// Creates a new [Media] with a byte [stream] of length [length] with a
+ /// [contentType].
+ ///
+ /// When uploading media, [length] can only be null if [ResumableUploadOptions]
+ /// is used.
Media(this.stream, this.length,
{this.contentType = 'application/octet-stream'}) {
if (stream == null || contentType == null) {
@@ -35,22 +31,18 @@
}
}
-/**
- * Represents options for uploading a [Media].
- */
+/// Represents options for uploading a [Media].
class UploadOptions {
- /** Use either simple uploads (only media) or multipart for media+metadata */
+ /// Use either simple uploads (only media) or multipart for media+metadata
static const UploadOptions Default = UploadOptions();
- /** Make resumable uploads */
+ /// Make resumable uploads
static final ResumableUploadOptions Resumable = ResumableUploadOptions();
const UploadOptions();
}
-/**
- * Specifies options for resumable uploads.
- */
+/// Specifies options for resumable uploads.
class ResumableUploadOptions extends UploadOptions {
static final core.Function ExponentialBackoff = (core.int failedAttempts) {
// Do not retry more than 5 times.
@@ -61,23 +53,17 @@
return core.Duration(seconds: 1 << (failedAttempts - 1));
};
- /**
- * Maximum number of upload attempts per chunk.
- */
+ /// Maximum number of upload attempts per chunk.
final core.int numberOfAttempts;
- /**
- * Preferred size (in bytes) of a uploaded chunk.
- * Must be a multiple of 256 KB.
- *
- * The default is 1 MB.
- */
+ /// Preferred size (in bytes) of a uploaded chunk.
+ /// Must be a multiple of 256 KB.
+ ///
+ /// The default is 1 MB.
final core.int chunkSize;
- /**
- * Function for determining the [core.Duration] to wait before making the
- * next attempt. See [ExponentialBackoff] for an example.
- */
+ /// Function for determining the [core.Duration] to wait before making the
+ /// next attempt. See [ExponentialBackoff] for an example.
final core.Function backoffFunction;
ResumableUploadOptions(
@@ -104,54 +90,46 @@
}
}
-/**
- * Represents options for downloading media.
- *
- * For partial downloads, see [PartialDownloadOptions].
- */
+/// Represents options for downloading media.
+///
+/// For partial downloads, see [PartialDownloadOptions].
class DownloadOptions {
- /** Download only metadata. */
+ /// Download only metadata.
static const DownloadOptions Metadata = DownloadOptions();
- /** Download full media. */
+ /// Download full media.
static final PartialDownloadOptions FullMedia =
PartialDownloadOptions(ByteRange(0, -1));
const DownloadOptions();
- /** Indicates whether metadata should be downloaded. */
+ /// Indicates whether metadata should be downloaded.
core.bool get isMetadataDownload => true;
}
-/**
- * Options for downloading a [Media].
- */
+/// Options for downloading a [Media].
class PartialDownloadOptions extends DownloadOptions {
- /** The range of bytes to be downloaded */
+ /// The range of bytes to be downloaded
final ByteRange range;
PartialDownloadOptions(this.range);
core.bool get isMetadataDownload => false;
- /**
- * `true` if this is a full download and `false` if this is a partial
- * download.
- */
+ /// `true` if this is a full download and `false` if this is a partial
+ /// download.
core.bool get isFullDownload => range.start == 0 && range.end == -1;
}
-/**
- * Specifies a range of media.
- */
+/// Specifies a range of media.
class ByteRange {
- /** First byte of media. */
+ /// First byte of media.
final core.int start;
- /** Last byte of media (inclusive) */
+ /// Last byte of media (inclusive)
final core.int end;
- /** Length of this range (i.e. number of bytes) */
+ /// Length of this range (i.e. number of bytes)
core.int get length => end - start + 1;
ByteRange(this.start, this.end) {
@@ -161,9 +139,7 @@
}
}
-/**
- * Represents a general error reported by the API endpoint.
- */
+/// Represents a general error reported by the API endpoint.
class ApiRequestError extends core.Error {
final core.String message;
@@ -172,9 +148,7 @@
core.String toString() => 'ApiRequestError(message: $message)';
}
-/**
- * Represents a specific error reported by the API endpoint.
- */
+/// Represents a specific error reported by the API endpoint.
class DetailedApiRequestError extends ApiRequestError {
/// The error code. For some non-google services this can be `null`.
final core.int status;
diff --git a/_discoveryapis_commons/lib/src/version_fallback.dart b/_discoveryapis_commons/lib/src/version_fallback.dart
new file mode 100644
index 0000000..1672372
--- /dev/null
+++ b/_discoveryapis_commons/lib/src/version_fallback.dart
@@ -0,0 +1,7 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Hardcoded dart version as `Platform` from `dart:io` is not available when
+/// targeting javascript.
+final dartVersion = '2.0.0';
diff --git a/_discoveryapis_commons/lib/src/version_io.dart b/_discoveryapis_commons/lib/src/version_io.dart
new file mode 100644
index 0000000..747fd6d
--- /dev/null
+++ b/_discoveryapis_commons/lib/src/version_io.dart
@@ -0,0 +1,8 @@
+// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:io' show Platform;
+
+/// Major.minor.patch version of current dart version.
+final dartVersion = Platform.version.split(RegExp('[^0-9]')).take(3).join('.');
diff --git a/_discoveryapis_commons/pubspec.yaml b/_discoveryapis_commons/pubspec.yaml
index b57c512..102d765 100644
--- a/_discoveryapis_commons/pubspec.yaml
+++ b/_discoveryapis_commons/pubspec.yaml
@@ -1,5 +1,5 @@
name: _discoveryapis_commons
-version: 0.1.8+1
+version: 0.1.9
author: Dart Team <misc@dartlang.org>
description: Library for use by client APIs generated from Discovery Documents.
homepage: https://github.com/dart-lang/discoveryapis_commons
diff --git a/grpc/.github/ISSUE_TEMPLATE b/grpc/.github/ISSUE_TEMPLATE
new file mode 100644
index 0000000..f9478b2
--- /dev/null
+++ b/grpc/.github/ISSUE_TEMPLATE
@@ -0,0 +1,17 @@
+<short description>
+
+<version of the grpc-dart packages used; see your `pubspec.lock` file>
+
+## Repro steps
+
+1. step1
+1. step2
+1. step3
+
+Expected result: <describe what should have happened>
+
+Actual result: <describe what actually happened>
+
+## Details
+
+<Include any other relevant details, logs, etc.>
diff --git a/grpc/.gitignore b/grpc/.gitignore
new file mode 100644
index 0000000..e851cc5
--- /dev/null
+++ b/grpc/.gitignore
@@ -0,0 +1,12 @@
+# Files and directories created by pub
+.dart_tool/
+.packages
+.pub/
+build/
+
+# Remove the following pattern if you wish to check in your lock file
+pubspec.lock
+
+# Directory created by dartdoc
+doc/api/
+
diff --git a/grpc/.travis.yml b/grpc/.travis.yml
new file mode 100644
index 0000000..24dde5c
--- /dev/null
+++ b/grpc/.travis.yml
@@ -0,0 +1,38 @@
+language: dart
+sudo: false
+
+# Run against both the dev and channel.
+dart:
+ - stable
+ - dev
+
+# Define test tasks to run.
+dart_task:
+ - test: --platform vm
+
+# Only run one instance of the formatter and the analyzer, rather than running
+# them against each Dart version.
+matrix:
+ include:
+ # Wish we could exclude `example` in `analysis_options.yaml` but it seems
+ # blocked by https://github.com/dart-lang/sdk/issues/26212
+ - dart: dev
+ dart_task: dartfmt
+ script:
+ - dartanalyzer lib test
+ - for example in example/*; do (cd $example; echo [Analyzing $example]; pub get; dartanalyzer .); done
+ - (cd interop; echo [Analyzing interop]; pub get; dartanalyzer .)
+
+
+# Only building master means that we don't run two builds for each pull request.
+branches:
+ only: [master]
+
+os:
+ - linux
+ - osx
+ - windows
+
+cache:
+ directories:
+ - $HOME/.pub-cache
diff --git a/grpc/AUTHORS b/grpc/AUTHORS
new file mode 100644
index 0000000..7a6111c
--- /dev/null
+++ b/grpc/AUTHORS
@@ -0,0 +1,8 @@
+# Below is a list of people and organizations that have contributed
+# to the project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google Inc.
+German Saprykin <saprykin.h@gmail.com>
+Alexandre Ardhuin <alexandre.ardhuin@gmail.com>
diff --git a/grpc/BUILD.gn b/grpc/BUILD.gn
new file mode 100644
index 0000000..96f2fa0
--- /dev/null
+++ b/grpc/BUILD.gn
@@ -0,0 +1,21 @@
+# This file is generated by importer.py for grpc-1.0.3
+
+import("//build/dart/dart_library.gni")
+
+dart_library("grpc") {
+ package_name = "grpc"
+
+ # This parameter is left empty as we don't care about analysis or exporting
+ # these sources outside of the tree.
+ sources = []
+
+ disable_analysis = true
+
+ deps = [
+ "//third_party/dart-pkg/pub/async",
+ "//third_party/dart-pkg/pub/meta",
+ "//third_party/dart-pkg/pub/googleapis_auth",
+ "//third_party/dart-pkg/pub/http",
+ "//third_party/dart-pkg/pub/http2",
+ ]
+}
diff --git a/grpc/CHANGELOG.md b/grpc/CHANGELOG.md
new file mode 100644
index 0000000..361870f
--- /dev/null
+++ b/grpc/CHANGELOG.md
@@ -0,0 +1,122 @@
+## 1.0.3
+
+* Allow custom user agent with a `userAgent` argument for `ChannelOptions()`.
+* Allow specifying `authority` for `ChannelCredentials.insecure()`.
+* Add `userAgent` as an optional named argument for `clientConnection.createCallHeaders()`.
+
+## 1.0.2
+
+* Fix bug where the server would crash if the client would break the connection.
+
+## 1.0.1
+
+* Add `service_api.dart` that only contains the minimal imports needed by the code generated by
+ protoc_plugin.
+
+## 1.0.0+1
+
+* Support package:http2 1.0.0.
+
+## 1.0.0
+
+* Graduate package to 1.0.
+
+## 0.6.8+1
+
+* Removes stray files that where published by accident in version 0.6.8.
+
+## 0.6.8
+
+* Calling `terminate()` or `shutdown()` on a channel doesn't throw error if the
+channel is not yet open.
+
+## 0.6.7
+
+* Support package:test 1.5.
+
+## 0.6.6
+
+* Support `package:http` `>=0.11.3+17 <0.13.0`.
+* Update `package:googleapis_auth` to `^0.2.5+3`.
+
+## 0.6.5
+
+* Interceptors are now async.
+
+## 0.6.4
+
+* Update dependencies to be compatible with Dart 2.
+
+## 0.6.3
+
+* Make fields of `StatusCode` const rather than final.
+
+## 0.6.2
+
+* Allow for non-ascii header values.
+
+## 0.6.1
+
+* More fixes to update to Dart 2 core library APIs.
+
+## 0.6.0+1
+
+* Updated implementation to use new Dart 2 APIs using
+[dart2_fix](https://github.com/dart-lang/dart2_fix).
+
+## 0.6.0
+
+* Dart SDK upper constraint raised to declare compatability with Dart 2.0 stable.
+
+## 0.5.0
+
+* Breaking change: The package now exclusively supports Dart 2.
+* Fixed tests to pass in Dart 2.
+* Added support for Interceptors ([issue #79](https://github.com/grpc/grpc-dart/issues/79)); thanks to [@mogol](https://github.com/mogol) for contributing!
+
+## 0.4.1
+
+* Fixes for supporting Dart 2.
+
+## 0.4.0
+
+* Moved TLS credentials for server into a separate class.
+* Added support for specifying the address for the server, and support for
+ serving on an ephemeral port.
+
+## 0.3.1
+
+* Split out TLS credentials to a separate class.
+
+## 0.3.0
+
+* Added authentication metadata providers, optimized for use with Google Cloud.
+* Added service URI to metadata provider API, needed for Json Web Token generation.
+* Added authenticated cloud-to-prod interoperability tests.
+* Refactored connection logic to throw initial connection errors early.
+
+## 0.2.1
+
+* Updated generated code in examples using latest protoc compiler plugin.
+* Dart 2.0 fixes.
+* Changed license to Apache 2.0.
+
+## 0.2.0
+
+* Implemented support for per-RPC metadata providers. This can be used for
+ authentication providers which may need to obtain or refresh a token before
+ the RPC is sent.
+
+## 0.1.0
+
+* Core gRPC functionality is implemented and passes
+[gRPC compliance tests](https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md).
+
+The API is shaping up, but may still change as more advanced features are implemented.
+
+## 0.0.1
+
+* Initial version.
+
+This package is in a very early and experimental state. We do not recommend
+using it for anything but experiments.
diff --git a/grpc/CODE-OF-CONDUCT.md b/grpc/CODE-OF-CONDUCT.md
new file mode 100644
index 0000000..9d4213e
--- /dev/null
+++ b/grpc/CODE-OF-CONDUCT.md
@@ -0,0 +1,3 @@
+## Community Code of Conduct
+
+gRPC follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
diff --git a/grpc/CONTRIBUTING.md b/grpc/CONTRIBUTING.md
new file mode 100644
index 0000000..b87c68f
--- /dev/null
+++ b/grpc/CONTRIBUTING.md
@@ -0,0 +1,65 @@
+# How to contribute
+
+We definitely welcome your patches and contributions to gRPC!
+
+If you are new to github, please start by reading [Pull Request
+howto](https://help.github.com/articles/about-pull-requests/)
+
+## Legal requirements
+
+In order to protect both you and ourselves, you will need to sign the
+[Contributor License
+Agreement](https://identity.linuxfoundation.org/projects/cncf).
+
+## Code style
+
+We follow the [Effective
+Dart](https://www.dartlang.org/guides/language/effective-dart/style) code style.
+We rely on auto-formatting all whitespace -- this avoids having to discuss
+formatting during reviews. To format your code, run `dartfmt` from the root, or
+use the similar action in your [favorite Dart
+editor](https://www.dartlang.org/tools).
+
+All code must pass Dart analysis. If you are using an IDE or Dart-enabled editor
+it should raise analysis issues as you edit; alternatively validate from the
+Terminal:
+
+```
+dartanalyzer lib test
+```
+
+All analysis warnings and errors must be fixed; hints should be considered.
+
+## Running tests
+
+```
+pub get
+pub run test
+```
+
+## Guidelines for Pull Requests
+
+How to get your contributions merged smoothly and quickly.
+
+- Create **small PRs** that are narrowly focused on **addressing a single
+concern**.
+
+- For speculative changes, consider opening an issue and discussing it first.
+
+- Provide a good **PR description** as a record of **what** change is being made
+and **why** it was made. Link to a github issue if it exists.
+
+- Unless your PR is trivial, you should expect there will be review comments
+that you'll need to address before merging. We expect you to be reasonably
+responsive to those comments, otherwise the PR will be closed after 2-3 weeks of
+inactivity.
+
+- Keep your PR up to date with upstream/master (if there are merge conflicts, we
+can't really merge your change).
+
+- **All tests need to be passing** before your change can be merged. We
+recommend you **run tests locally** before creating your PR to catch breakages
+early on.
+
+- Exceptions to the rules can be made if there's a compelling reason for doing
+so.
diff --git a/grpc/LICENSE b/grpc/LICENSE
new file mode 100644
index 0000000..c11903c
--- /dev/null
+++ b/grpc/LICENSE
@@ -0,0 +1,176 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
diff --git a/grpc/README.md b/grpc/README.md
new file mode 100644
index 0000000..1700aa8
--- /dev/null
+++ b/grpc/README.md
@@ -0,0 +1,22 @@
+The [Dart](https://www.dart.dev/) implementation of
+[gRPC](https://grpc.io/): A high performance, open source, general RPC framework that puts mobile and HTTP/2 first.
+
+[![Build Status](https://travis-ci.org/grpc/grpc-dart.svg?branch=master)](https://travis-ci.org/grpc/grpc-dart)
+[![pub package](https://img.shields.io/pub/v/grpc.svg)](https://pub.dev/packages/grpc)
+
+# Usage
+
+See the [Dart gRPC Quickstart](https://grpc.io/docs/quickstart/dart.html).
+
+# Status
+
+If you experience issues, or if you have feature requests,
+please [open an issue](https://github.com/dart-lang/grpc-dart/issues).
+
+# Notes
+
+This library requires Dart SDK version 2.0 or later.
+
+It currently supports the the [Flutter](https://flutter.dev/) and
+[Dart native](https://dart.dev/platforms) platforms. The potential
+addition of gRPC-Web is tracked in [issue 43](https://github.com/grpc/grpc-dart/issues/43).
diff --git a/grpc/analysis_options.yaml b/grpc/analysis_options.yaml
new file mode 100644
index 0000000..b2d4046
--- /dev/null
+++ b/grpc/analysis_options.yaml
@@ -0,0 +1,16 @@
+# Lint rules and documentation, see http://dart-lang.github.io/linter/lints
+linter:
+ rules:
+ - avoid_init_to_null
+ - cancel_subscriptions
+ - close_sinks
+ - directives_ordering
+ - hash_and_equals
+ - iterable_contains_unrelated_type
+ - list_remove_unrelated_type
+ - prefer_final_fields
+ - prefer_final_locals
+ - prefer_is_not_empty
+ - test_types_in_equals
+ - unrelated_type_equality_checks
+ - valid_regexps
diff --git a/grpc/example/README.md b/grpc/example/README.md
new file mode 100644
index 0000000..963d131
--- /dev/null
+++ b/grpc/example/README.md
@@ -0,0 +1,15 @@
+Four code examples are available:
+
+1. [`/helloworld/`](https://github.com/grpc/grpc-dart/tree/master/example/helloworld):
+ A demonstration of using the Dart gRPC library to perform unary RPs.
+
+1. [`/googleapis/`](https://github.com/grpc/grpc-dart/tree/master/example/googleapis):
+ A demonstration of using the Dart gRPC library to communicate with Google APIs.
+
+1. [`/metadata/`](https://github.com/grpc/grpc-dart/tree/master/example/metadata):
+ A demonstration of how to handle custom metadata, cancellation, and timeouts in Dart gRPC.
+
+1. [`/route_guide/`](https://github.com/grpc/grpc-dart/tree/master/example/route_guide):
+ A demonstration of how to perform unary, client streaming, server streaming and full duplex RPCs.
+
+For a complete, step-wise working example, see the [Dart gRPC Quickstart](https://grpc.io/docs/quickstart/dart.html).
diff --git a/grpc/example/googleapis/.gitignore b/grpc/example/googleapis/.gitignore
new file mode 100644
index 0000000..f67f1c8
--- /dev/null
+++ b/grpc/example/googleapis/.gitignore
@@ -0,0 +1 @@
+logging-service-account.json
diff --git a/grpc/example/googleapis/README.md b/grpc/example/googleapis/README.md
new file mode 100644
index 0000000..d78b5af
--- /dev/null
+++ b/grpc/example/googleapis/README.md
@@ -0,0 +1,62 @@
+# Description
+The googleapis client demonstrates how to use Dart gRPC libraries to communicate
+with Google APIs.
+
+# Set up Google Cloud Platform project
+This example uses the Stackdriver Logging API. Please follow the documentation on
+[Stackdriver Logging Documentation](https://cloud.google.com/logging/docs/) to create
+a project and enable the logging API.
+
+Then follow the documentation to
+[create a service account](https://developers.google.com/identity/protocols/OAuth2ServiceAccount#creatinganaccount).
+This example uses the Logging/Logs Writer role.
+
+Create a new service key, download the JSON file for it, and save it as
+`logging-service-account.json`.
+
+# Run the sample code
+To run the example, assuming you are in the root of the googleapis folder, i.e.,
+.../example/googleapis/, first get the dependencies by running:
+
+```sh
+$ pub get
+```
+
+Then, to run the logging client sample:
+
+```sh
+$ pub run googleapis:logging
+```
+
+# Regenerate the stubs
+The Dart gRPC stubs and message classes are generated based on protobuf definition
+files from [googleapis/googleapis](https://github.com/googleapis/googleapis).
+
+To regenerate them, you will need to check out both
+[googleapis/googleapis](https://github.com/googleapis/googleapis) and
+[google/protobuf](https://github.com/google/protobuf).
+
+You will also need to have protoc version 3.0.0 or higher and the Dart protoc
+plugin version 0.7.9 or higher on your PATH.
+
+To install protoc, see the instructions on
+[the Protocol Buffers website](https://developers.google.com/protocol-buffers/).
+
+The easiest way to get the Dart protoc plugin is by running
+
+```sh
+$ pub global activate protoc_plugin
+```
+
+and follow the directions to add `~/.pub-cache/bin` to your PATH, if you haven't
+already done so.
+
+You can now regenerate the Dart files. Set the `PROTOBUF` and `GOOGLEAPIS`
+environment variables to point to your clone of
+[google/protobuf](https://github.com/google/protobuf) and
+[googleapis/googleapis](https://github.com/googleapis/googleapis), respectively,
+and then run
+
+```sh
+$ tool/regenerate.sh
+```
diff --git a/grpc/example/googleapis/bin/logging.dart b/grpc/example/googleapis/bin/logging.dart
new file mode 100644
index 0000000..f505713
--- /dev/null
+++ b/grpc/example/googleapis/bin/logging.dart
@@ -0,0 +1,56 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:grpc/grpc.dart';
+
+import 'package:googleapis/src/generated/google/api/monitored_resource.pb.dart';
+import 'package:googleapis/src/generated/google/logging/type/log_severity.pb.dart';
+import 'package:googleapis/src/generated/google/logging/v2/log_entry.pb.dart';
+import 'package:googleapis/src/generated/google/logging/v2/logging.pbgrpc.dart';
+
+Future<void> main() async {
+ final serviceAccountFile = new File('logging-service-account.json');
+ if (!serviceAccountFile.existsSync()) {
+ print('File logging-service-account.json not found. Please follow the '
+ 'steps in README.md to create it.');
+ exit(-1);
+ }
+
+ final scopes = [
+ 'https://www.googleapis.com/auth/cloud-platform',
+ 'https://www.googleapis.com/auth/logging.write',
+ ];
+
+ final authenticator = new ServiceAccountAuthenticator(
+ serviceAccountFile.readAsStringSync(), scopes);
+ final projectId = authenticator.projectId;
+
+ final channel = new ClientChannel('logging.googleapis.com');
+ final logging =
+ new LoggingServiceV2Client(channel, options: authenticator.toCallOptions);
+
+ final request = new WriteLogEntriesRequest()
+ ..entries.add(new LogEntry()
+ ..logName = 'projects/$projectId/logs/example'
+ ..severity = LogSeverity.INFO
+ ..resource = (new MonitoredResource()..type = 'global')
+ ..textPayload = 'This is a log entry!');
+ await logging.writeLogEntries(request);
+
+ await channel.shutdown();
+}
diff --git a/grpc/example/googleapis/lib/src/generated/google/api/label.pb.dart b/grpc/example/googleapis/lib/src/generated/google/api/label.pb.dart
new file mode 100644
index 0000000..de8c072
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/api/label.pb.dart
@@ -0,0 +1,78 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.api_label;
+
+// ignore: UNUSED_SHOWN_NAME
+import 'dart:core' show int, bool, double, String, List, override;
+
+import 'package:protobuf/protobuf.dart';
+
+import 'label.pbenum.dart';
+
+export 'label.pbenum.dart';
+
+class LabelDescriptor extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('LabelDescriptor')
+ ..aOS(1, 'key')
+ ..e<LabelDescriptor_ValueType>(
+ 2,
+ 'valueType',
+ PbFieldType.OE,
+ LabelDescriptor_ValueType.STRING,
+ LabelDescriptor_ValueType.valueOf,
+ LabelDescriptor_ValueType.values)
+ ..aOS(3, 'description')
+ ..hasRequiredFields = false;
+
+ LabelDescriptor() : super();
+ LabelDescriptor.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ LabelDescriptor.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ LabelDescriptor clone() => new LabelDescriptor()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static LabelDescriptor create() => new LabelDescriptor();
+ static PbList<LabelDescriptor> createRepeated() =>
+ new PbList<LabelDescriptor>();
+ static LabelDescriptor getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlyLabelDescriptor();
+ return _defaultInstance;
+ }
+
+ static LabelDescriptor _defaultInstance;
+ static void $checkItem(LabelDescriptor v) {
+ if (v is! LabelDescriptor) checkItemFailed(v, 'LabelDescriptor');
+ }
+
+ String get key => $_getS(0, '');
+ set key(String v) {
+ $_setString(0, v);
+ }
+
+ bool hasKey() => $_has(0);
+ void clearKey() => clearField(1);
+
+ LabelDescriptor_ValueType get valueType => $_getN(1);
+ set valueType(LabelDescriptor_ValueType v) {
+ setField(2, v);
+ }
+
+ bool hasValueType() => $_has(1);
+ void clearValueType() => clearField(2);
+
+ String get description => $_getS(2, '');
+ set description(String v) {
+ $_setString(2, v);
+ }
+
+ bool hasDescription() => $_has(2);
+ void clearDescription() => clearField(3);
+}
+
+class _ReadonlyLabelDescriptor extends LabelDescriptor
+ with ReadonlyMessageMixin {}
diff --git a/grpc/example/googleapis/lib/src/generated/google/api/label.pbenum.dart b/grpc/example/googleapis/lib/src/generated/google/api/label.pbenum.dart
new file mode 100644
index 0000000..3badf8c
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/api/label.pbenum.dart
@@ -0,0 +1,35 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.api_label_pbenum;
+
+// ignore_for_file: UNDEFINED_SHOWN_NAME,UNUSED_SHOWN_NAME
+import 'dart:core' show int, dynamic, String, List, Map;
+import 'package:protobuf/protobuf.dart';
+
+class LabelDescriptor_ValueType extends ProtobufEnum {
+ static const LabelDescriptor_ValueType STRING =
+ const LabelDescriptor_ValueType._(0, 'STRING');
+ static const LabelDescriptor_ValueType BOOL =
+ const LabelDescriptor_ValueType._(1, 'BOOL');
+ static const LabelDescriptor_ValueType INT64 =
+ const LabelDescriptor_ValueType._(2, 'INT64');
+
+ static const List<LabelDescriptor_ValueType> values =
+ const <LabelDescriptor_ValueType>[
+ STRING,
+ BOOL,
+ INT64,
+ ];
+
+ static final Map<int, dynamic> _byValue = ProtobufEnum.initByValue(values);
+ static LabelDescriptor_ValueType valueOf(int value) =>
+ _byValue[value] as LabelDescriptor_ValueType;
+ static void $checkItem(LabelDescriptor_ValueType v) {
+ if (v is! LabelDescriptor_ValueType)
+ checkItemFailed(v, 'LabelDescriptor_ValueType');
+ }
+
+ const LabelDescriptor_ValueType._(int v, String n) : super(v, n);
+}
diff --git a/grpc/example/googleapis/lib/src/generated/google/api/label.pbjson.dart b/grpc/example/googleapis/lib/src/generated/google/api/label.pbjson.dart
new file mode 100644
index 0000000..36cc915
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/api/label.pbjson.dart
@@ -0,0 +1,31 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.api_label_pbjson;
+
+const LabelDescriptor$json = const {
+ '1': 'LabelDescriptor',
+ '2': const [
+ const {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'},
+ const {
+ '1': 'value_type',
+ '3': 2,
+ '4': 1,
+ '5': 14,
+ '6': '.google.api.LabelDescriptor.ValueType',
+ '10': 'valueType'
+ },
+ const {'1': 'description', '3': 3, '4': 1, '5': 9, '10': 'description'},
+ ],
+ '4': const [LabelDescriptor_ValueType$json],
+};
+
+const LabelDescriptor_ValueType$json = const {
+ '1': 'ValueType',
+ '2': const [
+ const {'1': 'STRING', '2': 0},
+ const {'1': 'BOOL', '2': 1},
+ const {'1': 'INT64', '2': 2},
+ ],
+};
diff --git a/grpc/example/googleapis/lib/src/generated/google/api/monitored_resource.pb.dart b/grpc/example/googleapis/lib/src/generated/google/api/monitored_resource.pb.dart
new file mode 100644
index 0000000..c75ee71
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/api/monitored_resource.pb.dart
@@ -0,0 +1,186 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.api_monitored_resource;
+
+// ignore: UNUSED_SHOWN_NAME
+import 'dart:core' show int, bool, double, String, List, override;
+
+import 'package:protobuf/protobuf.dart';
+
+import 'label.pb.dart';
+
+class MonitoredResourceDescriptor extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('MonitoredResourceDescriptor')
+ ..aOS(1, 'type')
+ ..aOS(2, 'displayName')
+ ..aOS(3, 'description')
+ ..pp<LabelDescriptor>(4, 'labels', PbFieldType.PM,
+ LabelDescriptor.$checkItem, LabelDescriptor.create)
+ ..aOS(5, 'name')
+ ..hasRequiredFields = false;
+
+ MonitoredResourceDescriptor() : super();
+ MonitoredResourceDescriptor.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ MonitoredResourceDescriptor.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ MonitoredResourceDescriptor clone() =>
+ new MonitoredResourceDescriptor()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static MonitoredResourceDescriptor create() =>
+ new MonitoredResourceDescriptor();
+ static PbList<MonitoredResourceDescriptor> createRepeated() =>
+ new PbList<MonitoredResourceDescriptor>();
+ static MonitoredResourceDescriptor getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlyMonitoredResourceDescriptor();
+ return _defaultInstance;
+ }
+
+ static MonitoredResourceDescriptor _defaultInstance;
+ static void $checkItem(MonitoredResourceDescriptor v) {
+ if (v is! MonitoredResourceDescriptor)
+ checkItemFailed(v, 'MonitoredResourceDescriptor');
+ }
+
+ String get type => $_getS(0, '');
+ set type(String v) {
+ $_setString(0, v);
+ }
+
+ bool hasType() => $_has(0);
+ void clearType() => clearField(1);
+
+ String get displayName => $_getS(1, '');
+ set displayName(String v) {
+ $_setString(1, v);
+ }
+
+ bool hasDisplayName() => $_has(1);
+ void clearDisplayName() => clearField(2);
+
+ String get description => $_getS(2, '');
+ set description(String v) {
+ $_setString(2, v);
+ }
+
+ bool hasDescription() => $_has(2);
+ void clearDescription() => clearField(3);
+
+ List<LabelDescriptor> get labels => $_getList(3);
+
+ String get name => $_getS(4, '');
+ set name(String v) {
+ $_setString(4, v);
+ }
+
+ bool hasName() => $_has(4);
+ void clearName() => clearField(5);
+}
+
+class _ReadonlyMonitoredResourceDescriptor extends MonitoredResourceDescriptor
+ with ReadonlyMessageMixin {}
+
+class MonitoredResource_LabelsEntry extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('MonitoredResource_LabelsEntry')
+ ..aOS(1, 'key')
+ ..aOS(2, 'value')
+ ..hasRequiredFields = false;
+
+ MonitoredResource_LabelsEntry() : super();
+ MonitoredResource_LabelsEntry.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ MonitoredResource_LabelsEntry.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ MonitoredResource_LabelsEntry clone() =>
+ new MonitoredResource_LabelsEntry()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static MonitoredResource_LabelsEntry create() =>
+ new MonitoredResource_LabelsEntry();
+ static PbList<MonitoredResource_LabelsEntry> createRepeated() =>
+ new PbList<MonitoredResource_LabelsEntry>();
+ static MonitoredResource_LabelsEntry getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlyMonitoredResource_LabelsEntry();
+ return _defaultInstance;
+ }
+
+ static MonitoredResource_LabelsEntry _defaultInstance;
+ static void $checkItem(MonitoredResource_LabelsEntry v) {
+ if (v is! MonitoredResource_LabelsEntry)
+ checkItemFailed(v, 'MonitoredResource_LabelsEntry');
+ }
+
+ String get key => $_getS(0, '');
+ set key(String v) {
+ $_setString(0, v);
+ }
+
+ bool hasKey() => $_has(0);
+ void clearKey() => clearField(1);
+
+ String get value => $_getS(1, '');
+ set value(String v) {
+ $_setString(1, v);
+ }
+
+ bool hasValue() => $_has(1);
+ void clearValue() => clearField(2);
+}
+
+class _ReadonlyMonitoredResource_LabelsEntry
+ extends MonitoredResource_LabelsEntry with ReadonlyMessageMixin {}
+
+class MonitoredResource extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('MonitoredResource')
+ ..aOS(1, 'type')
+ ..pp<MonitoredResource_LabelsEntry>(
+ 2,
+ 'labels',
+ PbFieldType.PM,
+ MonitoredResource_LabelsEntry.$checkItem,
+ MonitoredResource_LabelsEntry.create)
+ ..hasRequiredFields = false;
+
+ MonitoredResource() : super();
+ MonitoredResource.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ MonitoredResource.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ MonitoredResource clone() => new MonitoredResource()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static MonitoredResource create() => new MonitoredResource();
+ static PbList<MonitoredResource> createRepeated() =>
+ new PbList<MonitoredResource>();
+ static MonitoredResource getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlyMonitoredResource();
+ return _defaultInstance;
+ }
+
+ static MonitoredResource _defaultInstance;
+ static void $checkItem(MonitoredResource v) {
+ if (v is! MonitoredResource) checkItemFailed(v, 'MonitoredResource');
+ }
+
+ String get type => $_getS(0, '');
+ set type(String v) {
+ $_setString(0, v);
+ }
+
+ bool hasType() => $_has(0);
+ void clearType() => clearField(1);
+
+ List<MonitoredResource_LabelsEntry> get labels => $_getList(1);
+}
+
+class _ReadonlyMonitoredResource extends MonitoredResource
+ with ReadonlyMessageMixin {}
diff --git a/grpc/example/googleapis/lib/src/generated/google/api/monitored_resource.pbenum.dart b/grpc/example/googleapis/lib/src/generated/google/api/monitored_resource.pbenum.dart
new file mode 100644
index 0000000..ab8bdc9
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/api/monitored_resource.pbenum.dart
@@ -0,0 +1,5 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.api_monitored_resource_pbenum;
diff --git a/grpc/example/googleapis/lib/src/generated/google/api/monitored_resource.pbjson.dart b/grpc/example/googleapis/lib/src/generated/google/api/monitored_resource.pbjson.dart
new file mode 100644
index 0000000..746965b
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/api/monitored_resource.pbjson.dart
@@ -0,0 +1,48 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.api_monitored_resource_pbjson;
+
+const MonitoredResourceDescriptor$json = const {
+ '1': 'MonitoredResourceDescriptor',
+ '2': const [
+ const {'1': 'name', '3': 5, '4': 1, '5': 9, '10': 'name'},
+ const {'1': 'type', '3': 1, '4': 1, '5': 9, '10': 'type'},
+ const {'1': 'display_name', '3': 2, '4': 1, '5': 9, '10': 'displayName'},
+ const {'1': 'description', '3': 3, '4': 1, '5': 9, '10': 'description'},
+ const {
+ '1': 'labels',
+ '3': 4,
+ '4': 3,
+ '5': 11,
+ '6': '.google.api.LabelDescriptor',
+ '10': 'labels'
+ },
+ ],
+};
+
+const MonitoredResource$json = const {
+ '1': 'MonitoredResource',
+ '2': const [
+ const {'1': 'type', '3': 1, '4': 1, '5': 9, '10': 'type'},
+ const {
+ '1': 'labels',
+ '3': 2,
+ '4': 3,
+ '5': 11,
+ '6': '.google.api.MonitoredResource.LabelsEntry',
+ '10': 'labels'
+ },
+ ],
+ '3': const [MonitoredResource_LabelsEntry$json],
+};
+
+const MonitoredResource_LabelsEntry$json = const {
+ '1': 'LabelsEntry',
+ '2': const [
+ const {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'},
+ const {'1': 'value', '3': 2, '4': 1, '5': 9, '10': 'value'},
+ ],
+ '7': const {'7': true},
+};
diff --git a/grpc/example/googleapis/lib/src/generated/google/logging/type/http_request.pb.dart b/grpc/example/googleapis/lib/src/generated/google/logging/type/http_request.pb.dart
new file mode 100644
index 0000000..b90c01a
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/logging/type/http_request.pb.dart
@@ -0,0 +1,177 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.logging.type_http_request;
+
+// ignore: UNUSED_SHOWN_NAME
+import 'dart:core' show int, bool, double, String, List, override;
+
+import 'package:fixnum/fixnum.dart';
+import 'package:protobuf/protobuf.dart';
+
+import '../../protobuf/duration.pb.dart' as $google$protobuf;
+
+class HttpRequest extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('HttpRequest')
+ ..aOS(1, 'requestMethod')
+ ..aOS(2, 'requestUrl')
+ ..aInt64(3, 'requestSize')
+ ..a<int>(4, 'status', PbFieldType.O3)
+ ..aInt64(5, 'responseSize')
+ ..aOS(6, 'userAgent')
+ ..aOS(7, 'remoteIp')
+ ..aOS(8, 'referer')
+ ..aOB(9, 'cacheHit')
+ ..aOB(10, 'cacheValidatedWithOriginServer')
+ ..aOB(11, 'cacheLookup')
+ ..aInt64(12, 'cacheFillBytes')
+ ..aOS(13, 'serverIp')
+ ..a<$google$protobuf.Duration>(14, 'latency', PbFieldType.OM,
+ $google$protobuf.Duration.getDefault, $google$protobuf.Duration.create)
+ ..aOS(15, 'protocol')
+ ..hasRequiredFields = false;
+
+ HttpRequest() : super();
+ HttpRequest.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ HttpRequest.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ HttpRequest clone() => new HttpRequest()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static HttpRequest create() => new HttpRequest();
+ static PbList<HttpRequest> createRepeated() => new PbList<HttpRequest>();
+ static HttpRequest getDefault() {
+ if (_defaultInstance == null) _defaultInstance = new _ReadonlyHttpRequest();
+ return _defaultInstance;
+ }
+
+ static HttpRequest _defaultInstance;
+ static void $checkItem(HttpRequest v) {
+ if (v is! HttpRequest) checkItemFailed(v, 'HttpRequest');
+ }
+
+ String get requestMethod => $_getS(0, '');
+ set requestMethod(String v) {
+ $_setString(0, v);
+ }
+
+ bool hasRequestMethod() => $_has(0);
+ void clearRequestMethod() => clearField(1);
+
+ String get requestUrl => $_getS(1, '');
+ set requestUrl(String v) {
+ $_setString(1, v);
+ }
+
+ bool hasRequestUrl() => $_has(1);
+ void clearRequestUrl() => clearField(2);
+
+ Int64 get requestSize => $_getI64(2);
+ set requestSize(Int64 v) {
+ $_setInt64(2, v);
+ }
+
+ bool hasRequestSize() => $_has(2);
+ void clearRequestSize() => clearField(3);
+
+ int get status => $_get(3, 0);
+ set status(int v) {
+ $_setUnsignedInt32(3, v);
+ }
+
+ bool hasStatus() => $_has(3);
+ void clearStatus() => clearField(4);
+
+ Int64 get responseSize => $_getI64(4);
+ set responseSize(Int64 v) {
+ $_setInt64(4, v);
+ }
+
+ bool hasResponseSize() => $_has(4);
+ void clearResponseSize() => clearField(5);
+
+ String get userAgent => $_getS(5, '');
+ set userAgent(String v) {
+ $_setString(5, v);
+ }
+
+ bool hasUserAgent() => $_has(5);
+ void clearUserAgent() => clearField(6);
+
+ String get remoteIp => $_getS(6, '');
+ set remoteIp(String v) {
+ $_setString(6, v);
+ }
+
+ bool hasRemoteIp() => $_has(6);
+ void clearRemoteIp() => clearField(7);
+
+ String get referer => $_getS(7, '');
+ set referer(String v) {
+ $_setString(7, v);
+ }
+
+ bool hasReferer() => $_has(7);
+ void clearReferer() => clearField(8);
+
+ bool get cacheHit => $_get(8, false);
+ set cacheHit(bool v) {
+ $_setBool(8, v);
+ }
+
+ bool hasCacheHit() => $_has(8);
+ void clearCacheHit() => clearField(9);
+
+ bool get cacheValidatedWithOriginServer => $_get(9, false);
+ set cacheValidatedWithOriginServer(bool v) {
+ $_setBool(9, v);
+ }
+
+ bool hasCacheValidatedWithOriginServer() => $_has(9);
+ void clearCacheValidatedWithOriginServer() => clearField(10);
+
+ bool get cacheLookup => $_get(10, false);
+ set cacheLookup(bool v) {
+ $_setBool(10, v);
+ }
+
+ bool hasCacheLookup() => $_has(10);
+ void clearCacheLookup() => clearField(11);
+
+ Int64 get cacheFillBytes => $_getI64(11);
+ set cacheFillBytes(Int64 v) {
+ $_setInt64(11, v);
+ }
+
+ bool hasCacheFillBytes() => $_has(11);
+ void clearCacheFillBytes() => clearField(12);
+
+ String get serverIp => $_getS(12, '');
+ set serverIp(String v) {
+ $_setString(12, v);
+ }
+
+ bool hasServerIp() => $_has(12);
+ void clearServerIp() => clearField(13);
+
+ $google$protobuf.Duration get latency => $_getN(13);
+ set latency($google$protobuf.Duration v) {
+ setField(14, v);
+ }
+
+ bool hasLatency() => $_has(13);
+ void clearLatency() => clearField(14);
+
+ String get protocol => $_getS(14, '');
+ set protocol(String v) {
+ $_setString(14, v);
+ }
+
+ bool hasProtocol() => $_has(14);
+ void clearProtocol() => clearField(15);
+}
+
+class _ReadonlyHttpRequest extends HttpRequest with ReadonlyMessageMixin {}
diff --git a/grpc/example/googleapis/lib/src/generated/google/logging/type/http_request.pbenum.dart b/grpc/example/googleapis/lib/src/generated/google/logging/type/http_request.pbenum.dart
new file mode 100644
index 0000000..cb196dc
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/logging/type/http_request.pbenum.dart
@@ -0,0 +1,5 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.logging.type_http_request_pbenum;
diff --git a/grpc/example/googleapis/lib/src/generated/google/logging/type/http_request.pbjson.dart b/grpc/example/googleapis/lib/src/generated/google/logging/type/http_request.pbjson.dart
new file mode 100644
index 0000000..b9c176c
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/logging/type/http_request.pbjson.dart
@@ -0,0 +1,51 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.logging.type_http_request_pbjson;
+
+const HttpRequest$json = const {
+ '1': 'HttpRequest',
+ '2': const [
+ const {
+ '1': 'request_method',
+ '3': 1,
+ '4': 1,
+ '5': 9,
+ '10': 'requestMethod'
+ },
+ const {'1': 'request_url', '3': 2, '4': 1, '5': 9, '10': 'requestUrl'},
+ const {'1': 'request_size', '3': 3, '4': 1, '5': 3, '10': 'requestSize'},
+ const {'1': 'status', '3': 4, '4': 1, '5': 5, '10': 'status'},
+ const {'1': 'response_size', '3': 5, '4': 1, '5': 3, '10': 'responseSize'},
+ const {'1': 'user_agent', '3': 6, '4': 1, '5': 9, '10': 'userAgent'},
+ const {'1': 'remote_ip', '3': 7, '4': 1, '5': 9, '10': 'remoteIp'},
+ const {'1': 'server_ip', '3': 13, '4': 1, '5': 9, '10': 'serverIp'},
+ const {'1': 'referer', '3': 8, '4': 1, '5': 9, '10': 'referer'},
+ const {
+ '1': 'latency',
+ '3': 14,
+ '4': 1,
+ '5': 11,
+ '6': '.google.protobuf.Duration',
+ '10': 'latency'
+ },
+ const {'1': 'cache_lookup', '3': 11, '4': 1, '5': 8, '10': 'cacheLookup'},
+ const {'1': 'cache_hit', '3': 9, '4': 1, '5': 8, '10': 'cacheHit'},
+ const {
+ '1': 'cache_validated_with_origin_server',
+ '3': 10,
+ '4': 1,
+ '5': 8,
+ '10': 'cacheValidatedWithOriginServer'
+ },
+ const {
+ '1': 'cache_fill_bytes',
+ '3': 12,
+ '4': 1,
+ '5': 3,
+ '10': 'cacheFillBytes'
+ },
+ const {'1': 'protocol', '3': 15, '4': 1, '5': 9, '10': 'protocol'},
+ ],
+};
diff --git a/grpc/example/googleapis/lib/src/generated/google/logging/type/log_severity.pb.dart b/grpc/example/googleapis/lib/src/generated/google/logging/type/log_severity.pb.dart
new file mode 100644
index 0000000..e215075
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/logging/type/log_severity.pb.dart
@@ -0,0 +1,10 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.logging.type_log_severity;
+
+// ignore: UNUSED_SHOWN_NAME
+import 'dart:core' show int, bool, double, String, List, override;
+
+export 'log_severity.pbenum.dart';
diff --git a/grpc/example/googleapis/lib/src/generated/google/logging/type/log_severity.pbenum.dart b/grpc/example/googleapis/lib/src/generated/google/logging/type/log_severity.pbenum.dart
new file mode 100644
index 0000000..1141dd7
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/logging/type/log_severity.pbenum.dart
@@ -0,0 +1,41 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.logging.type_log_severity_pbenum;
+
+// ignore_for_file: UNDEFINED_SHOWN_NAME,UNUSED_SHOWN_NAME
+import 'dart:core' show int, dynamic, String, List, Map;
+import 'package:protobuf/protobuf.dart';
+
+class LogSeverity extends ProtobufEnum {
+ static const LogSeverity DEFAULT = const LogSeverity._(0, 'DEFAULT');
+ static const LogSeverity DEBUG = const LogSeverity._(100, 'DEBUG');
+ static const LogSeverity INFO = const LogSeverity._(200, 'INFO');
+ static const LogSeverity NOTICE = const LogSeverity._(300, 'NOTICE');
+ static const LogSeverity WARNING = const LogSeverity._(400, 'WARNING');
+ static const LogSeverity ERROR = const LogSeverity._(500, 'ERROR');
+ static const LogSeverity CRITICAL = const LogSeverity._(600, 'CRITICAL');
+ static const LogSeverity ALERT = const LogSeverity._(700, 'ALERT');
+ static const LogSeverity EMERGENCY = const LogSeverity._(800, 'EMERGENCY');
+
+ static const List<LogSeverity> values = const <LogSeverity>[
+ DEFAULT,
+ DEBUG,
+ INFO,
+ NOTICE,
+ WARNING,
+ ERROR,
+ CRITICAL,
+ ALERT,
+ EMERGENCY,
+ ];
+
+ static final Map<int, dynamic> _byValue = ProtobufEnum.initByValue(values);
+ static LogSeverity valueOf(int value) => _byValue[value] as LogSeverity;
+ static void $checkItem(LogSeverity v) {
+ if (v is! LogSeverity) checkItemFailed(v, 'LogSeverity');
+ }
+
+ const LogSeverity._(int v, String n) : super(v, n);
+}
diff --git a/grpc/example/googleapis/lib/src/generated/google/logging/type/log_severity.pbjson.dart b/grpc/example/googleapis/lib/src/generated/google/logging/type/log_severity.pbjson.dart
new file mode 100644
index 0000000..45b2646
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/logging/type/log_severity.pbjson.dart
@@ -0,0 +1,20 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.logging.type_log_severity_pbjson;
+
+const LogSeverity$json = const {
+ '1': 'LogSeverity',
+ '2': const [
+ const {'1': 'DEFAULT', '2': 0},
+ const {'1': 'DEBUG', '2': 100},
+ const {'1': 'INFO', '2': 200},
+ const {'1': 'NOTICE', '2': 300},
+ const {'1': 'WARNING', '2': 400},
+ const {'1': 'ERROR', '2': 500},
+ const {'1': 'CRITICAL', '2': 600},
+ const {'1': 'ALERT', '2': 700},
+ const {'1': 'EMERGENCY', '2': 800},
+ ],
+};
diff --git a/grpc/example/googleapis/lib/src/generated/google/logging/v2/log_entry.pb.dart b/grpc/example/googleapis/lib/src/generated/google/logging/v2/log_entry.pb.dart
new file mode 100644
index 0000000..9b42389
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/logging/v2/log_entry.pb.dart
@@ -0,0 +1,383 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.logging.v2_log_entry;
+
+// ignore: UNUSED_SHOWN_NAME
+import 'dart:core' show int, bool, double, String, List, override;
+
+import 'package:fixnum/fixnum.dart';
+import 'package:protobuf/protobuf.dart';
+
+import '../../protobuf/any.pb.dart' as $google$protobuf;
+import '../../protobuf/struct.pb.dart' as $google$protobuf;
+import '../type/http_request.pb.dart' as $google$logging$type;
+import '../../api/monitored_resource.pb.dart' as $google$api;
+import '../../protobuf/timestamp.pb.dart' as $google$protobuf;
+
+import '../type/log_severity.pbenum.dart' as $google$logging$type;
+
+class LogEntry_LabelsEntry extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('LogEntry_LabelsEntry')
+ ..aOS(1, 'key')
+ ..aOS(2, 'value')
+ ..hasRequiredFields = false;
+
+ LogEntry_LabelsEntry() : super();
+ LogEntry_LabelsEntry.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ LogEntry_LabelsEntry.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ LogEntry_LabelsEntry clone() =>
+ new LogEntry_LabelsEntry()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static LogEntry_LabelsEntry create() => new LogEntry_LabelsEntry();
+ static PbList<LogEntry_LabelsEntry> createRepeated() =>
+ new PbList<LogEntry_LabelsEntry>();
+ static LogEntry_LabelsEntry getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlyLogEntry_LabelsEntry();
+ return _defaultInstance;
+ }
+
+ static LogEntry_LabelsEntry _defaultInstance;
+ static void $checkItem(LogEntry_LabelsEntry v) {
+ if (v is! LogEntry_LabelsEntry) checkItemFailed(v, 'LogEntry_LabelsEntry');
+ }
+
+ String get key => $_getS(0, '');
+ set key(String v) {
+ $_setString(0, v);
+ }
+
+ bool hasKey() => $_has(0);
+ void clearKey() => clearField(1);
+
+ String get value => $_getS(1, '');
+ set value(String v) {
+ $_setString(1, v);
+ }
+
+ bool hasValue() => $_has(1);
+ void clearValue() => clearField(2);
+}
+
+class _ReadonlyLogEntry_LabelsEntry extends LogEntry_LabelsEntry
+ with ReadonlyMessageMixin {}
+
+class LogEntry extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('LogEntry')
+ ..a<$google$protobuf.Any>(2, 'protoPayload', PbFieldType.OM,
+ $google$protobuf.Any.getDefault, $google$protobuf.Any.create)
+ ..aOS(3, 'textPayload')
+ ..aOS(4, 'insertId')
+ ..a<$google$protobuf.Struct>(6, 'jsonPayload', PbFieldType.OM,
+ $google$protobuf.Struct.getDefault, $google$protobuf.Struct.create)
+ ..a<$google$logging$type.HttpRequest>(
+ 7,
+ 'httpRequest',
+ PbFieldType.OM,
+ $google$logging$type.HttpRequest.getDefault,
+ $google$logging$type.HttpRequest.create)
+ ..a<$google$api.MonitoredResource>(
+ 8,
+ 'resource',
+ PbFieldType.OM,
+ $google$api.MonitoredResource.getDefault,
+ $google$api.MonitoredResource.create)
+ ..a<$google$protobuf.Timestamp>(
+ 9,
+ 'timestamp',
+ PbFieldType.OM,
+ $google$protobuf.Timestamp.getDefault,
+ $google$protobuf.Timestamp.create)
+ ..e<$google$logging$type.LogSeverity>(
+ 10,
+ 'severity',
+ PbFieldType.OE,
+ $google$logging$type.LogSeverity.DEFAULT,
+ $google$logging$type.LogSeverity.valueOf,
+ $google$logging$type.LogSeverity.values)
+ ..pp<LogEntry_LabelsEntry>(11, 'labels', PbFieldType.PM,
+ LogEntry_LabelsEntry.$checkItem, LogEntry_LabelsEntry.create)
+ ..aOS(12, 'logName')
+ ..a<LogEntryOperation>(15, 'operation', PbFieldType.OM,
+ LogEntryOperation.getDefault, LogEntryOperation.create)
+ ..aOS(22, 'trace')
+ ..a<LogEntrySourceLocation>(23, 'sourceLocation', PbFieldType.OM,
+ LogEntrySourceLocation.getDefault, LogEntrySourceLocation.create)
+ ..a<$google$protobuf.Timestamp>(
+ 24,
+ 'receiveTimestamp',
+ PbFieldType.OM,
+ $google$protobuf.Timestamp.getDefault,
+ $google$protobuf.Timestamp.create)
+ ..aOS(27, 'spanId')
+ ..hasRequiredFields = false;
+
+ LogEntry() : super();
+ LogEntry.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ LogEntry.fromJson(String i, [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ LogEntry clone() => new LogEntry()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static LogEntry create() => new LogEntry();
+ static PbList<LogEntry> createRepeated() => new PbList<LogEntry>();
+ static LogEntry getDefault() {
+ if (_defaultInstance == null) _defaultInstance = new _ReadonlyLogEntry();
+ return _defaultInstance;
+ }
+
+ static LogEntry _defaultInstance;
+ static void $checkItem(LogEntry v) {
+ if (v is! LogEntry) checkItemFailed(v, 'LogEntry');
+ }
+
+ $google$protobuf.Any get protoPayload => $_getN(0);
+ set protoPayload($google$protobuf.Any v) {
+ setField(2, v);
+ }
+
+ bool hasProtoPayload() => $_has(0);
+ void clearProtoPayload() => clearField(2);
+
+ String get textPayload => $_getS(1, '');
+ set textPayload(String v) {
+ $_setString(1, v);
+ }
+
+ bool hasTextPayload() => $_has(1);
+ void clearTextPayload() => clearField(3);
+
+ String get insertId => $_getS(2, '');
+ set insertId(String v) {
+ $_setString(2, v);
+ }
+
+ bool hasInsertId() => $_has(2);
+ void clearInsertId() => clearField(4);
+
+ $google$protobuf.Struct get jsonPayload => $_getN(3);
+ set jsonPayload($google$protobuf.Struct v) {
+ setField(6, v);
+ }
+
+ bool hasJsonPayload() => $_has(3);
+ void clearJsonPayload() => clearField(6);
+
+ $google$logging$type.HttpRequest get httpRequest => $_getN(4);
+ set httpRequest($google$logging$type.HttpRequest v) {
+ setField(7, v);
+ }
+
+ bool hasHttpRequest() => $_has(4);
+ void clearHttpRequest() => clearField(7);
+
+ $google$api.MonitoredResource get resource => $_getN(5);
+ set resource($google$api.MonitoredResource v) {
+ setField(8, v);
+ }
+
+ bool hasResource() => $_has(5);
+ void clearResource() => clearField(8);
+
+ $google$protobuf.Timestamp get timestamp => $_getN(6);
+ set timestamp($google$protobuf.Timestamp v) {
+ setField(9, v);
+ }
+
+ bool hasTimestamp() => $_has(6);
+ void clearTimestamp() => clearField(9);
+
+ $google$logging$type.LogSeverity get severity => $_getN(7);
+ set severity($google$logging$type.LogSeverity v) {
+ setField(10, v);
+ }
+
+ bool hasSeverity() => $_has(7);
+ void clearSeverity() => clearField(10);
+
+ List<LogEntry_LabelsEntry> get labels => $_getList(8);
+
+ String get logName => $_getS(9, '');
+ set logName(String v) {
+ $_setString(9, v);
+ }
+
+ bool hasLogName() => $_has(9);
+ void clearLogName() => clearField(12);
+
+ LogEntryOperation get operation => $_getN(10);
+ set operation(LogEntryOperation v) {
+ setField(15, v);
+ }
+
+ bool hasOperation() => $_has(10);
+ void clearOperation() => clearField(15);
+
+ String get trace => $_getS(11, '');
+ set trace(String v) {
+ $_setString(11, v);
+ }
+
+ bool hasTrace() => $_has(11);
+ void clearTrace() => clearField(22);
+
+ LogEntrySourceLocation get sourceLocation => $_getN(12);
+ set sourceLocation(LogEntrySourceLocation v) {
+ setField(23, v);
+ }
+
+ bool hasSourceLocation() => $_has(12);
+ void clearSourceLocation() => clearField(23);
+
+ $google$protobuf.Timestamp get receiveTimestamp => $_getN(13);
+ set receiveTimestamp($google$protobuf.Timestamp v) {
+ setField(24, v);
+ }
+
+ bool hasReceiveTimestamp() => $_has(13);
+ void clearReceiveTimestamp() => clearField(24);
+
+ String get spanId => $_getS(14, '');
+ set spanId(String v) {
+ $_setString(14, v);
+ }
+
+ bool hasSpanId() => $_has(14);
+ void clearSpanId() => clearField(27);
+}
+
+class _ReadonlyLogEntry extends LogEntry with ReadonlyMessageMixin {}
+
+class LogEntryOperation extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('LogEntryOperation')
+ ..aOS(1, 'id')
+ ..aOS(2, 'producer')
+ ..aOB(3, 'first')
+ ..aOB(4, 'last')
+ ..hasRequiredFields = false;
+
+ LogEntryOperation() : super();
+ LogEntryOperation.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ LogEntryOperation.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ LogEntryOperation clone() => new LogEntryOperation()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static LogEntryOperation create() => new LogEntryOperation();
+ static PbList<LogEntryOperation> createRepeated() =>
+ new PbList<LogEntryOperation>();
+ static LogEntryOperation getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlyLogEntryOperation();
+ return _defaultInstance;
+ }
+
+ static LogEntryOperation _defaultInstance;
+ static void $checkItem(LogEntryOperation v) {
+ if (v is! LogEntryOperation) checkItemFailed(v, 'LogEntryOperation');
+ }
+
+ String get id => $_getS(0, '');
+ set id(String v) {
+ $_setString(0, v);
+ }
+
+ bool hasId() => $_has(0);
+ void clearId() => clearField(1);
+
+ String get producer => $_getS(1, '');
+ set producer(String v) {
+ $_setString(1, v);
+ }
+
+ bool hasProducer() => $_has(1);
+ void clearProducer() => clearField(2);
+
+ bool get first => $_get(2, false);
+ set first(bool v) {
+ $_setBool(2, v);
+ }
+
+ bool hasFirst() => $_has(2);
+ void clearFirst() => clearField(3);
+
+ bool get last => $_get(3, false);
+ set last(bool v) {
+ $_setBool(3, v);
+ }
+
+ bool hasLast() => $_has(3);
+ void clearLast() => clearField(4);
+}
+
+class _ReadonlyLogEntryOperation extends LogEntryOperation
+ with ReadonlyMessageMixin {}
+
+class LogEntrySourceLocation extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('LogEntrySourceLocation')
+ ..aOS(1, 'file')
+ ..aInt64(2, 'line')
+ ..aOS(3, 'function')
+ ..hasRequiredFields = false;
+
+ LogEntrySourceLocation() : super();
+ LogEntrySourceLocation.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ LogEntrySourceLocation.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ LogEntrySourceLocation clone() =>
+ new LogEntrySourceLocation()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static LogEntrySourceLocation create() => new LogEntrySourceLocation();
+ static PbList<LogEntrySourceLocation> createRepeated() =>
+ new PbList<LogEntrySourceLocation>();
+ static LogEntrySourceLocation getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlyLogEntrySourceLocation();
+ return _defaultInstance;
+ }
+
+ static LogEntrySourceLocation _defaultInstance;
+ static void $checkItem(LogEntrySourceLocation v) {
+ if (v is! LogEntrySourceLocation)
+ checkItemFailed(v, 'LogEntrySourceLocation');
+ }
+
+ String get file => $_getS(0, '');
+ set file(String v) {
+ $_setString(0, v);
+ }
+
+ bool hasFile() => $_has(0);
+ void clearFile() => clearField(1);
+
+ Int64 get line => $_getI64(1);
+ set line(Int64 v) {
+ $_setInt64(1, v);
+ }
+
+ bool hasLine() => $_has(1);
+ void clearLine() => clearField(2);
+
+ String get function => $_getS(2, '');
+ set function(String v) {
+ $_setString(2, v);
+ }
+
+ bool hasFunction() => $_has(2);
+ void clearFunction() => clearField(3);
+}
+
+class _ReadonlyLogEntrySourceLocation extends LogEntrySourceLocation
+ with ReadonlyMessageMixin {}
diff --git a/grpc/example/googleapis/lib/src/generated/google/logging/v2/log_entry.pbenum.dart b/grpc/example/googleapis/lib/src/generated/google/logging/v2/log_entry.pbenum.dart
new file mode 100644
index 0000000..e8f1a7f
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/logging/v2/log_entry.pbenum.dart
@@ -0,0 +1,5 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.logging.v2_log_entry_pbenum;
diff --git a/grpc/example/googleapis/lib/src/generated/google/logging/v2/log_entry.pbjson.dart b/grpc/example/googleapis/lib/src/generated/google/logging/v2/log_entry.pbjson.dart
new file mode 100644
index 0000000..117d400
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/logging/v2/log_entry.pbjson.dart
@@ -0,0 +1,137 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.logging.v2_log_entry_pbjson;
+
+const LogEntry$json = const {
+ '1': 'LogEntry',
+ '2': const [
+ const {'1': 'log_name', '3': 12, '4': 1, '5': 9, '10': 'logName'},
+ const {
+ '1': 'resource',
+ '3': 8,
+ '4': 1,
+ '5': 11,
+ '6': '.google.api.MonitoredResource',
+ '10': 'resource'
+ },
+ const {
+ '1': 'proto_payload',
+ '3': 2,
+ '4': 1,
+ '5': 11,
+ '6': '.google.protobuf.Any',
+ '9': 0,
+ '10': 'protoPayload'
+ },
+ const {
+ '1': 'text_payload',
+ '3': 3,
+ '4': 1,
+ '5': 9,
+ '9': 0,
+ '10': 'textPayload'
+ },
+ const {
+ '1': 'json_payload',
+ '3': 6,
+ '4': 1,
+ '5': 11,
+ '6': '.google.protobuf.Struct',
+ '9': 0,
+ '10': 'jsonPayload'
+ },
+ const {
+ '1': 'timestamp',
+ '3': 9,
+ '4': 1,
+ '5': 11,
+ '6': '.google.protobuf.Timestamp',
+ '10': 'timestamp'
+ },
+ const {
+ '1': 'receive_timestamp',
+ '3': 24,
+ '4': 1,
+ '5': 11,
+ '6': '.google.protobuf.Timestamp',
+ '10': 'receiveTimestamp'
+ },
+ const {
+ '1': 'severity',
+ '3': 10,
+ '4': 1,
+ '5': 14,
+ '6': '.google.logging.type.LogSeverity',
+ '10': 'severity'
+ },
+ const {'1': 'insert_id', '3': 4, '4': 1, '5': 9, '10': 'insertId'},
+ const {
+ '1': 'http_request',
+ '3': 7,
+ '4': 1,
+ '5': 11,
+ '6': '.google.logging.type.HttpRequest',
+ '10': 'httpRequest'
+ },
+ const {
+ '1': 'labels',
+ '3': 11,
+ '4': 3,
+ '5': 11,
+ '6': '.google.logging.v2.LogEntry.LabelsEntry',
+ '10': 'labels'
+ },
+ const {
+ '1': 'operation',
+ '3': 15,
+ '4': 1,
+ '5': 11,
+ '6': '.google.logging.v2.LogEntryOperation',
+ '10': 'operation'
+ },
+ const {'1': 'trace', '3': 22, '4': 1, '5': 9, '10': 'trace'},
+ const {'1': 'span_id', '3': 27, '4': 1, '5': 9, '10': 'spanId'},
+ const {
+ '1': 'source_location',
+ '3': 23,
+ '4': 1,
+ '5': 11,
+ '6': '.google.logging.v2.LogEntrySourceLocation',
+ '10': 'sourceLocation'
+ },
+ ],
+ '3': const [LogEntry_LabelsEntry$json],
+ '8': const [
+ const {'1': 'payload'},
+ ],
+};
+
+const LogEntry_LabelsEntry$json = const {
+ '1': 'LabelsEntry',
+ '2': const [
+ const {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'},
+ const {'1': 'value', '3': 2, '4': 1, '5': 9, '10': 'value'},
+ ],
+ '7': const {'7': true},
+};
+
+const LogEntryOperation$json = const {
+ '1': 'LogEntryOperation',
+ '2': const [
+ const {'1': 'id', '3': 1, '4': 1, '5': 9, '10': 'id'},
+ const {'1': 'producer', '3': 2, '4': 1, '5': 9, '10': 'producer'},
+ const {'1': 'first', '3': 3, '4': 1, '5': 8, '10': 'first'},
+ const {'1': 'last', '3': 4, '4': 1, '5': 8, '10': 'last'},
+ ],
+};
+
+const LogEntrySourceLocation$json = const {
+ '1': 'LogEntrySourceLocation',
+ '2': const [
+ const {'1': 'file', '3': 1, '4': 1, '5': 9, '10': 'file'},
+ const {'1': 'line', '3': 2, '4': 1, '5': 3, '10': 'line'},
+ const {'1': 'function', '3': 3, '4': 1, '5': 9, '10': 'function'},
+ ],
+};
diff --git a/grpc/example/googleapis/lib/src/generated/google/logging/v2/logging.pb.dart b/grpc/example/googleapis/lib/src/generated/google/logging/v2/logging.pb.dart
new file mode 100644
index 0000000..7156969
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/logging/v2/logging.pb.dart
@@ -0,0 +1,649 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.logging.v2_logging;
+
+// ignore: UNUSED_SHOWN_NAME
+import 'dart:core' show int, bool, double, String, List, override;
+
+import 'package:protobuf/protobuf.dart';
+
+import '../../api/monitored_resource.pb.dart' as $google$api;
+import 'log_entry.pb.dart';
+import '../../rpc/status.pb.dart' as $google$rpc;
+
+class DeleteLogRequest extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('DeleteLogRequest')
+ ..aOS(1, 'logName')
+ ..hasRequiredFields = false;
+
+ DeleteLogRequest() : super();
+ DeleteLogRequest.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ DeleteLogRequest.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ DeleteLogRequest clone() => new DeleteLogRequest()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static DeleteLogRequest create() => new DeleteLogRequest();
+ static PbList<DeleteLogRequest> createRepeated() =>
+ new PbList<DeleteLogRequest>();
+ static DeleteLogRequest getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlyDeleteLogRequest();
+ return _defaultInstance;
+ }
+
+ static DeleteLogRequest _defaultInstance;
+ static void $checkItem(DeleteLogRequest v) {
+ if (v is! DeleteLogRequest) checkItemFailed(v, 'DeleteLogRequest');
+ }
+
+ String get logName => $_getS(0, '');
+ set logName(String v) {
+ $_setString(0, v);
+ }
+
+ bool hasLogName() => $_has(0);
+ void clearLogName() => clearField(1);
+}
+
+class _ReadonlyDeleteLogRequest extends DeleteLogRequest
+ with ReadonlyMessageMixin {}
+
+class WriteLogEntriesRequest_LabelsEntry extends GeneratedMessage {
+ static final BuilderInfo _i =
+ new BuilderInfo('WriteLogEntriesRequest_LabelsEntry')
+ ..aOS(1, 'key')
+ ..aOS(2, 'value')
+ ..hasRequiredFields = false;
+
+ WriteLogEntriesRequest_LabelsEntry() : super();
+ WriteLogEntriesRequest_LabelsEntry.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ WriteLogEntriesRequest_LabelsEntry.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ WriteLogEntriesRequest_LabelsEntry clone() =>
+ new WriteLogEntriesRequest_LabelsEntry()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static WriteLogEntriesRequest_LabelsEntry create() =>
+ new WriteLogEntriesRequest_LabelsEntry();
+ static PbList<WriteLogEntriesRequest_LabelsEntry> createRepeated() =>
+ new PbList<WriteLogEntriesRequest_LabelsEntry>();
+ static WriteLogEntriesRequest_LabelsEntry getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlyWriteLogEntriesRequest_LabelsEntry();
+ return _defaultInstance;
+ }
+
+ static WriteLogEntriesRequest_LabelsEntry _defaultInstance;
+ static void $checkItem(WriteLogEntriesRequest_LabelsEntry v) {
+ if (v is! WriteLogEntriesRequest_LabelsEntry)
+ checkItemFailed(v, 'WriteLogEntriesRequest_LabelsEntry');
+ }
+
+ String get key => $_getS(0, '');
+ set key(String v) {
+ $_setString(0, v);
+ }
+
+ bool hasKey() => $_has(0);
+ void clearKey() => clearField(1);
+
+ String get value => $_getS(1, '');
+ set value(String v) {
+ $_setString(1, v);
+ }
+
+ bool hasValue() => $_has(1);
+ void clearValue() => clearField(2);
+}
+
+class _ReadonlyWriteLogEntriesRequest_LabelsEntry
+ extends WriteLogEntriesRequest_LabelsEntry with ReadonlyMessageMixin {}
+
+class WriteLogEntriesRequest extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('WriteLogEntriesRequest')
+ ..aOS(1, 'logName')
+ ..a<$google$api.MonitoredResource>(
+ 2,
+ 'resource',
+ PbFieldType.OM,
+ $google$api.MonitoredResource.getDefault,
+ $google$api.MonitoredResource.create)
+ ..pp<WriteLogEntriesRequest_LabelsEntry>(
+ 3,
+ 'labels',
+ PbFieldType.PM,
+ WriteLogEntriesRequest_LabelsEntry.$checkItem,
+ WriteLogEntriesRequest_LabelsEntry.create)
+ ..pp<LogEntry>(
+ 4, 'entries', PbFieldType.PM, LogEntry.$checkItem, LogEntry.create)
+ ..aOB(5, 'partialSuccess')
+ ..hasRequiredFields = false;
+
+ WriteLogEntriesRequest() : super();
+ WriteLogEntriesRequest.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ WriteLogEntriesRequest.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ WriteLogEntriesRequest clone() =>
+ new WriteLogEntriesRequest()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static WriteLogEntriesRequest create() => new WriteLogEntriesRequest();
+ static PbList<WriteLogEntriesRequest> createRepeated() =>
+ new PbList<WriteLogEntriesRequest>();
+ static WriteLogEntriesRequest getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlyWriteLogEntriesRequest();
+ return _defaultInstance;
+ }
+
+ static WriteLogEntriesRequest _defaultInstance;
+ static void $checkItem(WriteLogEntriesRequest v) {
+ if (v is! WriteLogEntriesRequest)
+ checkItemFailed(v, 'WriteLogEntriesRequest');
+ }
+
+ String get logName => $_getS(0, '');
+ set logName(String v) {
+ $_setString(0, v);
+ }
+
+ bool hasLogName() => $_has(0);
+ void clearLogName() => clearField(1);
+
+ $google$api.MonitoredResource get resource => $_getN(1);
+ set resource($google$api.MonitoredResource v) {
+ setField(2, v);
+ }
+
+ bool hasResource() => $_has(1);
+ void clearResource() => clearField(2);
+
+ List<WriteLogEntriesRequest_LabelsEntry> get labels => $_getList(2);
+
+ List<LogEntry> get entries => $_getList(3);
+
+ bool get partialSuccess => $_get(4, false);
+ set partialSuccess(bool v) {
+ $_setBool(4, v);
+ }
+
+ bool hasPartialSuccess() => $_has(4);
+ void clearPartialSuccess() => clearField(5);
+}
+
+class _ReadonlyWriteLogEntriesRequest extends WriteLogEntriesRequest
+ with ReadonlyMessageMixin {}
+
+class WriteLogEntriesResponse extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('WriteLogEntriesResponse')
+ ..hasRequiredFields = false;
+
+ WriteLogEntriesResponse() : super();
+ WriteLogEntriesResponse.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ WriteLogEntriesResponse.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ WriteLogEntriesResponse clone() =>
+ new WriteLogEntriesResponse()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static WriteLogEntriesResponse create() => new WriteLogEntriesResponse();
+ static PbList<WriteLogEntriesResponse> createRepeated() =>
+ new PbList<WriteLogEntriesResponse>();
+ static WriteLogEntriesResponse getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlyWriteLogEntriesResponse();
+ return _defaultInstance;
+ }
+
+ static WriteLogEntriesResponse _defaultInstance;
+ static void $checkItem(WriteLogEntriesResponse v) {
+ if (v is! WriteLogEntriesResponse)
+ checkItemFailed(v, 'WriteLogEntriesResponse');
+ }
+}
+
+class _ReadonlyWriteLogEntriesResponse extends WriteLogEntriesResponse
+ with ReadonlyMessageMixin {}
+
+class WriteLogEntriesPartialErrors_LogEntryErrorsEntry
+ extends GeneratedMessage {
+ static final BuilderInfo _i =
+ new BuilderInfo('WriteLogEntriesPartialErrors_LogEntryErrorsEntry')
+ ..a<int>(1, 'key', PbFieldType.O3)
+ ..a<$google$rpc.Status>(2, 'value', PbFieldType.OM,
+ $google$rpc.Status.getDefault, $google$rpc.Status.create)
+ ..hasRequiredFields = false;
+
+ WriteLogEntriesPartialErrors_LogEntryErrorsEntry() : super();
+ WriteLogEntriesPartialErrors_LogEntryErrorsEntry.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ WriteLogEntriesPartialErrors_LogEntryErrorsEntry.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ WriteLogEntriesPartialErrors_LogEntryErrorsEntry clone() =>
+ new WriteLogEntriesPartialErrors_LogEntryErrorsEntry()
+ ..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static WriteLogEntriesPartialErrors_LogEntryErrorsEntry create() =>
+ new WriteLogEntriesPartialErrors_LogEntryErrorsEntry();
+ static PbList<WriteLogEntriesPartialErrors_LogEntryErrorsEntry>
+ createRepeated() =>
+ new PbList<WriteLogEntriesPartialErrors_LogEntryErrorsEntry>();
+ static WriteLogEntriesPartialErrors_LogEntryErrorsEntry getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance =
+ new _ReadonlyWriteLogEntriesPartialErrors_LogEntryErrorsEntry();
+ return _defaultInstance;
+ }
+
+ static WriteLogEntriesPartialErrors_LogEntryErrorsEntry _defaultInstance;
+ static void $checkItem(WriteLogEntriesPartialErrors_LogEntryErrorsEntry v) {
+ if (v is! WriteLogEntriesPartialErrors_LogEntryErrorsEntry)
+ checkItemFailed(v, 'WriteLogEntriesPartialErrors_LogEntryErrorsEntry');
+ }
+
+ int get key => $_get(0, 0);
+ set key(int v) {
+ $_setUnsignedInt32(0, v);
+ }
+
+ bool hasKey() => $_has(0);
+ void clearKey() => clearField(1);
+
+ $google$rpc.Status get value => $_getN(1);
+ set value($google$rpc.Status v) {
+ setField(2, v);
+ }
+
+ bool hasValue() => $_has(1);
+ void clearValue() => clearField(2);
+}
+
+class _ReadonlyWriteLogEntriesPartialErrors_LogEntryErrorsEntry
+ extends WriteLogEntriesPartialErrors_LogEntryErrorsEntry
+ with ReadonlyMessageMixin {}
+
+class WriteLogEntriesPartialErrors extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('WriteLogEntriesPartialErrors')
+ ..pp<WriteLogEntriesPartialErrors_LogEntryErrorsEntry>(
+ 1,
+ 'logEntryErrors',
+ PbFieldType.PM,
+ WriteLogEntriesPartialErrors_LogEntryErrorsEntry.$checkItem,
+ WriteLogEntriesPartialErrors_LogEntryErrorsEntry.create)
+ ..hasRequiredFields = false;
+
+ WriteLogEntriesPartialErrors() : super();
+ WriteLogEntriesPartialErrors.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ WriteLogEntriesPartialErrors.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ WriteLogEntriesPartialErrors clone() =>
+ new WriteLogEntriesPartialErrors()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static WriteLogEntriesPartialErrors create() =>
+ new WriteLogEntriesPartialErrors();
+ static PbList<WriteLogEntriesPartialErrors> createRepeated() =>
+ new PbList<WriteLogEntriesPartialErrors>();
+ static WriteLogEntriesPartialErrors getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlyWriteLogEntriesPartialErrors();
+ return _defaultInstance;
+ }
+
+ static WriteLogEntriesPartialErrors _defaultInstance;
+ static void $checkItem(WriteLogEntriesPartialErrors v) {
+ if (v is! WriteLogEntriesPartialErrors)
+ checkItemFailed(v, 'WriteLogEntriesPartialErrors');
+ }
+
+ List<WriteLogEntriesPartialErrors_LogEntryErrorsEntry> get logEntryErrors =>
+ $_getList(0);
+}
+
+class _ReadonlyWriteLogEntriesPartialErrors extends WriteLogEntriesPartialErrors
+ with ReadonlyMessageMixin {}
+
+class ListLogEntriesRequest extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('ListLogEntriesRequest')
+ ..pPS(1, 'projectIds')
+ ..aOS(2, 'filter')
+ ..aOS(3, 'orderBy')
+ ..a<int>(4, 'pageSize', PbFieldType.O3)
+ ..aOS(5, 'pageToken')
+ ..pPS(8, 'resourceNames')
+ ..hasRequiredFields = false;
+
+ ListLogEntriesRequest() : super();
+ ListLogEntriesRequest.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ ListLogEntriesRequest.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ ListLogEntriesRequest clone() =>
+ new ListLogEntriesRequest()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static ListLogEntriesRequest create() => new ListLogEntriesRequest();
+ static PbList<ListLogEntriesRequest> createRepeated() =>
+ new PbList<ListLogEntriesRequest>();
+ static ListLogEntriesRequest getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlyListLogEntriesRequest();
+ return _defaultInstance;
+ }
+
+ static ListLogEntriesRequest _defaultInstance;
+ static void $checkItem(ListLogEntriesRequest v) {
+ if (v is! ListLogEntriesRequest)
+ checkItemFailed(v, 'ListLogEntriesRequest');
+ }
+
+ List<String> get projectIds => $_getList(0);
+
+ String get filter => $_getS(1, '');
+ set filter(String v) {
+ $_setString(1, v);
+ }
+
+ bool hasFilter() => $_has(1);
+ void clearFilter() => clearField(2);
+
+ String get orderBy => $_getS(2, '');
+ set orderBy(String v) {
+ $_setString(2, v);
+ }
+
+ bool hasOrderBy() => $_has(2);
+ void clearOrderBy() => clearField(3);
+
+ int get pageSize => $_get(3, 0);
+ set pageSize(int v) {
+ $_setUnsignedInt32(3, v);
+ }
+
+ bool hasPageSize() => $_has(3);
+ void clearPageSize() => clearField(4);
+
+ String get pageToken => $_getS(4, '');
+ set pageToken(String v) {
+ $_setString(4, v);
+ }
+
+ bool hasPageToken() => $_has(4);
+ void clearPageToken() => clearField(5);
+
+ List<String> get resourceNames => $_getList(5);
+}
+
+class _ReadonlyListLogEntriesRequest extends ListLogEntriesRequest
+ with ReadonlyMessageMixin {}
+
+class ListLogEntriesResponse extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('ListLogEntriesResponse')
+ ..pp<LogEntry>(
+ 1, 'entries', PbFieldType.PM, LogEntry.$checkItem, LogEntry.create)
+ ..aOS(2, 'nextPageToken')
+ ..hasRequiredFields = false;
+
+ ListLogEntriesResponse() : super();
+ ListLogEntriesResponse.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ ListLogEntriesResponse.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ ListLogEntriesResponse clone() =>
+ new ListLogEntriesResponse()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static ListLogEntriesResponse create() => new ListLogEntriesResponse();
+ static PbList<ListLogEntriesResponse> createRepeated() =>
+ new PbList<ListLogEntriesResponse>();
+ static ListLogEntriesResponse getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlyListLogEntriesResponse();
+ return _defaultInstance;
+ }
+
+ static ListLogEntriesResponse _defaultInstance;
+ static void $checkItem(ListLogEntriesResponse v) {
+ if (v is! ListLogEntriesResponse)
+ checkItemFailed(v, 'ListLogEntriesResponse');
+ }
+
+ List<LogEntry> get entries => $_getList(0);
+
+ String get nextPageToken => $_getS(1, '');
+ set nextPageToken(String v) {
+ $_setString(1, v);
+ }
+
+ bool hasNextPageToken() => $_has(1);
+ void clearNextPageToken() => clearField(2);
+}
+
+class _ReadonlyListLogEntriesResponse extends ListLogEntriesResponse
+ with ReadonlyMessageMixin {}
+
+class ListMonitoredResourceDescriptorsRequest extends GeneratedMessage {
+ static final BuilderInfo _i =
+ new BuilderInfo('ListMonitoredResourceDescriptorsRequest')
+ ..a<int>(1, 'pageSize', PbFieldType.O3)
+ ..aOS(2, 'pageToken')
+ ..hasRequiredFields = false;
+
+ ListMonitoredResourceDescriptorsRequest() : super();
+ ListMonitoredResourceDescriptorsRequest.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ ListMonitoredResourceDescriptorsRequest.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ ListMonitoredResourceDescriptorsRequest clone() =>
+ new ListMonitoredResourceDescriptorsRequest()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static ListMonitoredResourceDescriptorsRequest create() =>
+ new ListMonitoredResourceDescriptorsRequest();
+ static PbList<ListMonitoredResourceDescriptorsRequest> createRepeated() =>
+ new PbList<ListMonitoredResourceDescriptorsRequest>();
+ static ListMonitoredResourceDescriptorsRequest getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlyListMonitoredResourceDescriptorsRequest();
+ return _defaultInstance;
+ }
+
+ static ListMonitoredResourceDescriptorsRequest _defaultInstance;
+ static void $checkItem(ListMonitoredResourceDescriptorsRequest v) {
+ if (v is! ListMonitoredResourceDescriptorsRequest)
+ checkItemFailed(v, 'ListMonitoredResourceDescriptorsRequest');
+ }
+
+ int get pageSize => $_get(0, 0);
+ set pageSize(int v) {
+ $_setUnsignedInt32(0, v);
+ }
+
+ bool hasPageSize() => $_has(0);
+ void clearPageSize() => clearField(1);
+
+ String get pageToken => $_getS(1, '');
+ set pageToken(String v) {
+ $_setString(1, v);
+ }
+
+ bool hasPageToken() => $_has(1);
+ void clearPageToken() => clearField(2);
+}
+
+class _ReadonlyListMonitoredResourceDescriptorsRequest
+ extends ListMonitoredResourceDescriptorsRequest with ReadonlyMessageMixin {}
+
+class ListMonitoredResourceDescriptorsResponse extends GeneratedMessage {
+ static final BuilderInfo _i =
+ new BuilderInfo('ListMonitoredResourceDescriptorsResponse')
+ ..pp<$google$api.MonitoredResourceDescriptor>(
+ 1,
+ 'resourceDescriptors',
+ PbFieldType.PM,
+ $google$api.MonitoredResourceDescriptor.$checkItem,
+ $google$api.MonitoredResourceDescriptor.create)
+ ..aOS(2, 'nextPageToken')
+ ..hasRequiredFields = false;
+
+ ListMonitoredResourceDescriptorsResponse() : super();
+ ListMonitoredResourceDescriptorsResponse.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ ListMonitoredResourceDescriptorsResponse.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ ListMonitoredResourceDescriptorsResponse clone() =>
+ new ListMonitoredResourceDescriptorsResponse()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static ListMonitoredResourceDescriptorsResponse create() =>
+ new ListMonitoredResourceDescriptorsResponse();
+ static PbList<ListMonitoredResourceDescriptorsResponse> createRepeated() =>
+ new PbList<ListMonitoredResourceDescriptorsResponse>();
+ static ListMonitoredResourceDescriptorsResponse getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance =
+ new _ReadonlyListMonitoredResourceDescriptorsResponse();
+ return _defaultInstance;
+ }
+
+ static ListMonitoredResourceDescriptorsResponse _defaultInstance;
+ static void $checkItem(ListMonitoredResourceDescriptorsResponse v) {
+ if (v is! ListMonitoredResourceDescriptorsResponse)
+ checkItemFailed(v, 'ListMonitoredResourceDescriptorsResponse');
+ }
+
+ List<$google$api.MonitoredResourceDescriptor> get resourceDescriptors =>
+ $_getList(0);
+
+ String get nextPageToken => $_getS(1, '');
+ set nextPageToken(String v) {
+ $_setString(1, v);
+ }
+
+ bool hasNextPageToken() => $_has(1);
+ void clearNextPageToken() => clearField(2);
+}
+
+class _ReadonlyListMonitoredResourceDescriptorsResponse
+ extends ListMonitoredResourceDescriptorsResponse with ReadonlyMessageMixin {
+}
+
+class ListLogsRequest extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('ListLogsRequest')
+ ..aOS(1, 'parent')
+ ..a<int>(2, 'pageSize', PbFieldType.O3)
+ ..aOS(3, 'pageToken')
+ ..hasRequiredFields = false;
+
+ ListLogsRequest() : super();
+ ListLogsRequest.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ ListLogsRequest.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ ListLogsRequest clone() => new ListLogsRequest()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static ListLogsRequest create() => new ListLogsRequest();
+ static PbList<ListLogsRequest> createRepeated() =>
+ new PbList<ListLogsRequest>();
+ static ListLogsRequest getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlyListLogsRequest();
+ return _defaultInstance;
+ }
+
+ static ListLogsRequest _defaultInstance;
+ static void $checkItem(ListLogsRequest v) {
+ if (v is! ListLogsRequest) checkItemFailed(v, 'ListLogsRequest');
+ }
+
+ String get parent => $_getS(0, '');
+ set parent(String v) {
+ $_setString(0, v);
+ }
+
+ bool hasParent() => $_has(0);
+ void clearParent() => clearField(1);
+
+ int get pageSize => $_get(1, 0);
+ set pageSize(int v) {
+ $_setUnsignedInt32(1, v);
+ }
+
+ bool hasPageSize() => $_has(1);
+ void clearPageSize() => clearField(2);
+
+ String get pageToken => $_getS(2, '');
+ set pageToken(String v) {
+ $_setString(2, v);
+ }
+
+ bool hasPageToken() => $_has(2);
+ void clearPageToken() => clearField(3);
+}
+
+class _ReadonlyListLogsRequest extends ListLogsRequest
+ with ReadonlyMessageMixin {}
+
+class ListLogsResponse extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('ListLogsResponse')
+ ..aOS(2, 'nextPageToken')
+ ..pPS(3, 'logNames')
+ ..hasRequiredFields = false;
+
+ ListLogsResponse() : super();
+ ListLogsResponse.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ ListLogsResponse.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ ListLogsResponse clone() => new ListLogsResponse()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static ListLogsResponse create() => new ListLogsResponse();
+ static PbList<ListLogsResponse> createRepeated() =>
+ new PbList<ListLogsResponse>();
+ static ListLogsResponse getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlyListLogsResponse();
+ return _defaultInstance;
+ }
+
+ static ListLogsResponse _defaultInstance;
+ static void $checkItem(ListLogsResponse v) {
+ if (v is! ListLogsResponse) checkItemFailed(v, 'ListLogsResponse');
+ }
+
+ String get nextPageToken => $_getS(0, '');
+ set nextPageToken(String v) {
+ $_setString(0, v);
+ }
+
+ bool hasNextPageToken() => $_has(0);
+ void clearNextPageToken() => clearField(2);
+
+ List<String> get logNames => $_getList(1);
+}
+
+class _ReadonlyListLogsResponse extends ListLogsResponse
+ with ReadonlyMessageMixin {}
diff --git a/grpc/example/googleapis/lib/src/generated/google/logging/v2/logging.pbenum.dart b/grpc/example/googleapis/lib/src/generated/google/logging/v2/logging.pbenum.dart
new file mode 100644
index 0000000..d7ea628
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/logging/v2/logging.pbenum.dart
@@ -0,0 +1,5 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.logging.v2_logging_pbenum;
diff --git a/grpc/example/googleapis/lib/src/generated/google/logging/v2/logging.pbgrpc.dart b/grpc/example/googleapis/lib/src/generated/google/logging/v2/logging.pbgrpc.dart
new file mode 100644
index 0000000..0e65060
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/logging/v2/logging.pbgrpc.dart
@@ -0,0 +1,170 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.logging.v2_logging_pbgrpc;
+
+import 'dart:async';
+
+import 'package:grpc/grpc.dart';
+
+import 'logging.pb.dart';
+import '../../protobuf/empty.pb.dart' as $google$protobuf;
+export 'logging.pb.dart';
+
+class LoggingServiceV2Client extends Client {
+ static final _$deleteLog =
+ new ClientMethod<DeleteLogRequest, $google$protobuf.Empty>(
+ '/google.logging.v2.LoggingServiceV2/DeleteLog',
+ (DeleteLogRequest value) => value.writeToBuffer(),
+ (List<int> value) => new $google$protobuf.Empty.fromBuffer(value));
+ static final _$writeLogEntries =
+ new ClientMethod<WriteLogEntriesRequest, WriteLogEntriesResponse>(
+ '/google.logging.v2.LoggingServiceV2/WriteLogEntries',
+ (WriteLogEntriesRequest value) => value.writeToBuffer(),
+ (List<int> value) => new WriteLogEntriesResponse.fromBuffer(value));
+ static final _$listLogEntries =
+ new ClientMethod<ListLogEntriesRequest, ListLogEntriesResponse>(
+ '/google.logging.v2.LoggingServiceV2/ListLogEntries',
+ (ListLogEntriesRequest value) => value.writeToBuffer(),
+ (List<int> value) => new ListLogEntriesResponse.fromBuffer(value));
+ static final _$listMonitoredResourceDescriptors = new ClientMethod<
+ ListMonitoredResourceDescriptorsRequest,
+ ListMonitoredResourceDescriptorsResponse>(
+ '/google.logging.v2.LoggingServiceV2/ListMonitoredResourceDescriptors',
+ (ListMonitoredResourceDescriptorsRequest value) => value.writeToBuffer(),
+ (List<int> value) =>
+ new ListMonitoredResourceDescriptorsResponse.fromBuffer(value));
+ static final _$listLogs = new ClientMethod<ListLogsRequest, ListLogsResponse>(
+ '/google.logging.v2.LoggingServiceV2/ListLogs',
+ (ListLogsRequest value) => value.writeToBuffer(),
+ (List<int> value) => new ListLogsResponse.fromBuffer(value));
+
+ LoggingServiceV2Client(ClientChannel channel, {CallOptions options})
+ : super(channel, options: options);
+
+ ResponseFuture<$google$protobuf.Empty> deleteLog(DeleteLogRequest request,
+ {CallOptions options}) {
+ final call = $createCall(_$deleteLog, new Stream.fromIterable([request]),
+ options: options);
+ return new ResponseFuture(call);
+ }
+
+ ResponseFuture<WriteLogEntriesResponse> writeLogEntries(
+ WriteLogEntriesRequest request,
+ {CallOptions options}) {
+ final call = $createCall(
+ _$writeLogEntries, new Stream.fromIterable([request]),
+ options: options);
+ return new ResponseFuture(call);
+ }
+
+ ResponseFuture<ListLogEntriesResponse> listLogEntries(
+ ListLogEntriesRequest request,
+ {CallOptions options}) {
+ final call = $createCall(
+ _$listLogEntries, new Stream.fromIterable([request]),
+ options: options);
+ return new ResponseFuture(call);
+ }
+
+ ResponseFuture<ListMonitoredResourceDescriptorsResponse>
+ listMonitoredResourceDescriptors(
+ ListMonitoredResourceDescriptorsRequest request,
+ {CallOptions options}) {
+ final call = $createCall(
+ _$listMonitoredResourceDescriptors, new Stream.fromIterable([request]),
+ options: options);
+ return new ResponseFuture(call);
+ }
+
+ ResponseFuture<ListLogsResponse> listLogs(ListLogsRequest request,
+ {CallOptions options}) {
+ final call = $createCall(_$listLogs, new Stream.fromIterable([request]),
+ options: options);
+ return new ResponseFuture(call);
+ }
+}
+
+abstract class LoggingServiceV2ServiceBase extends Service {
+ String get $name => 'google.logging.v2.LoggingServiceV2';
+
+ LoggingServiceV2ServiceBase() {
+ $addMethod(new ServiceMethod<DeleteLogRequest, $google$protobuf.Empty>(
+ 'DeleteLog',
+ deleteLog_Pre,
+ false,
+ false,
+ (List<int> value) => new DeleteLogRequest.fromBuffer(value),
+ ($google$protobuf.Empty value) => value.writeToBuffer()));
+ $addMethod(
+ new ServiceMethod<WriteLogEntriesRequest, WriteLogEntriesResponse>(
+ 'WriteLogEntries',
+ writeLogEntries_Pre,
+ false,
+ false,
+ (List<int> value) => new WriteLogEntriesRequest.fromBuffer(value),
+ (WriteLogEntriesResponse value) => value.writeToBuffer()));
+ $addMethod(new ServiceMethod<ListLogEntriesRequest, ListLogEntriesResponse>(
+ 'ListLogEntries',
+ listLogEntries_Pre,
+ false,
+ false,
+ (List<int> value) => new ListLogEntriesRequest.fromBuffer(value),
+ (ListLogEntriesResponse value) => value.writeToBuffer()));
+ $addMethod(new ServiceMethod<ListMonitoredResourceDescriptorsRequest,
+ ListMonitoredResourceDescriptorsResponse>(
+ 'ListMonitoredResourceDescriptors',
+ listMonitoredResourceDescriptors_Pre,
+ false,
+ false,
+ (List<int> value) =>
+ new ListMonitoredResourceDescriptorsRequest.fromBuffer(value),
+ (ListMonitoredResourceDescriptorsResponse value) =>
+ value.writeToBuffer()));
+ $addMethod(new ServiceMethod<ListLogsRequest, ListLogsResponse>(
+ 'ListLogs',
+ listLogs_Pre,
+ false,
+ false,
+ (List<int> value) => new ListLogsRequest.fromBuffer(value),
+ (ListLogsResponse value) => value.writeToBuffer()));
+ }
+
+ Future<$google$protobuf.Empty> deleteLog_Pre(
+ ServiceCall call, Future request) async {
+ return deleteLog(call, await request);
+ }
+
+ Future<WriteLogEntriesResponse> writeLogEntries_Pre(
+ ServiceCall call, Future request) async {
+ return writeLogEntries(call, await request);
+ }
+
+ Future<ListLogEntriesResponse> listLogEntries_Pre(
+ ServiceCall call, Future request) async {
+ return listLogEntries(call, await request);
+ }
+
+ Future<ListMonitoredResourceDescriptorsResponse>
+ listMonitoredResourceDescriptors_Pre(
+ ServiceCall call, Future request) async {
+ return listMonitoredResourceDescriptors(call, await request);
+ }
+
+ Future<ListLogsResponse> listLogs_Pre(
+ ServiceCall call, Future request) async {
+ return listLogs(call, await request);
+ }
+
+ Future<$google$protobuf.Empty> deleteLog(
+ ServiceCall call, DeleteLogRequest request);
+ Future<WriteLogEntriesResponse> writeLogEntries(
+ ServiceCall call, WriteLogEntriesRequest request);
+ Future<ListLogEntriesResponse> listLogEntries(
+ ServiceCall call, ListLogEntriesRequest request);
+ Future<ListMonitoredResourceDescriptorsResponse>
+ listMonitoredResourceDescriptors(
+ ServiceCall call, ListMonitoredResourceDescriptorsRequest request);
+ Future<ListLogsResponse> listLogs(ServiceCall call, ListLogsRequest request);
+}
diff --git a/grpc/example/googleapis/lib/src/generated/google/logging/v2/logging.pbjson.dart b/grpc/example/googleapis/lib/src/generated/google/logging/v2/logging.pbjson.dart
new file mode 100644
index 0000000..9533338
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/logging/v2/logging.pbjson.dart
@@ -0,0 +1,187 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.logging.v2_logging_pbjson;
+
+const DeleteLogRequest$json = const {
+ '1': 'DeleteLogRequest',
+ '2': const [
+ const {'1': 'log_name', '3': 1, '4': 1, '5': 9, '10': 'logName'},
+ ],
+};
+
+const WriteLogEntriesRequest$json = const {
+ '1': 'WriteLogEntriesRequest',
+ '2': const [
+ const {'1': 'log_name', '3': 1, '4': 1, '5': 9, '10': 'logName'},
+ const {
+ '1': 'resource',
+ '3': 2,
+ '4': 1,
+ '5': 11,
+ '6': '.google.api.MonitoredResource',
+ '10': 'resource'
+ },
+ const {
+ '1': 'labels',
+ '3': 3,
+ '4': 3,
+ '5': 11,
+ '6': '.google.logging.v2.WriteLogEntriesRequest.LabelsEntry',
+ '10': 'labels'
+ },
+ const {
+ '1': 'entries',
+ '3': 4,
+ '4': 3,
+ '5': 11,
+ '6': '.google.logging.v2.LogEntry',
+ '10': 'entries'
+ },
+ const {
+ '1': 'partial_success',
+ '3': 5,
+ '4': 1,
+ '5': 8,
+ '10': 'partialSuccess'
+ },
+ ],
+ '3': const [WriteLogEntriesRequest_LabelsEntry$json],
+};
+
+const WriteLogEntriesRequest_LabelsEntry$json = const {
+ '1': 'LabelsEntry',
+ '2': const [
+ const {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'},
+ const {'1': 'value', '3': 2, '4': 1, '5': 9, '10': 'value'},
+ ],
+ '7': const {'7': true},
+};
+
+const WriteLogEntriesResponse$json = const {
+ '1': 'WriteLogEntriesResponse',
+};
+
+const WriteLogEntriesPartialErrors$json = const {
+ '1': 'WriteLogEntriesPartialErrors',
+ '2': const [
+ const {
+ '1': 'log_entry_errors',
+ '3': 1,
+ '4': 3,
+ '5': 11,
+ '6':
+ '.google.logging.v2.WriteLogEntriesPartialErrors.LogEntryErrorsEntry',
+ '10': 'logEntryErrors'
+ },
+ ],
+ '3': const [WriteLogEntriesPartialErrors_LogEntryErrorsEntry$json],
+};
+
+const WriteLogEntriesPartialErrors_LogEntryErrorsEntry$json = const {
+ '1': 'LogEntryErrorsEntry',
+ '2': const [
+ const {'1': 'key', '3': 1, '4': 1, '5': 5, '10': 'key'},
+ const {
+ '1': 'value',
+ '3': 2,
+ '4': 1,
+ '5': 11,
+ '6': '.google.rpc.Status',
+ '10': 'value'
+ },
+ ],
+ '7': const {'7': true},
+};
+
+const ListLogEntriesRequest$json = const {
+ '1': 'ListLogEntriesRequest',
+ '2': const [
+ const {'1': 'project_ids', '3': 1, '4': 3, '5': 9, '10': 'projectIds'},
+ const {
+ '1': 'resource_names',
+ '3': 8,
+ '4': 3,
+ '5': 9,
+ '10': 'resourceNames'
+ },
+ const {'1': 'filter', '3': 2, '4': 1, '5': 9, '10': 'filter'},
+ const {'1': 'order_by', '3': 3, '4': 1, '5': 9, '10': 'orderBy'},
+ const {'1': 'page_size', '3': 4, '4': 1, '5': 5, '10': 'pageSize'},
+ const {'1': 'page_token', '3': 5, '4': 1, '5': 9, '10': 'pageToken'},
+ ],
+};
+
+const ListLogEntriesResponse$json = const {
+ '1': 'ListLogEntriesResponse',
+ '2': const [
+ const {
+ '1': 'entries',
+ '3': 1,
+ '4': 3,
+ '5': 11,
+ '6': '.google.logging.v2.LogEntry',
+ '10': 'entries'
+ },
+ const {
+ '1': 'next_page_token',
+ '3': 2,
+ '4': 1,
+ '5': 9,
+ '10': 'nextPageToken'
+ },
+ ],
+};
+
+const ListMonitoredResourceDescriptorsRequest$json = const {
+ '1': 'ListMonitoredResourceDescriptorsRequest',
+ '2': const [
+ const {'1': 'page_size', '3': 1, '4': 1, '5': 5, '10': 'pageSize'},
+ const {'1': 'page_token', '3': 2, '4': 1, '5': 9, '10': 'pageToken'},
+ ],
+};
+
+const ListMonitoredResourceDescriptorsResponse$json = const {
+ '1': 'ListMonitoredResourceDescriptorsResponse',
+ '2': const [
+ const {
+ '1': 'resource_descriptors',
+ '3': 1,
+ '4': 3,
+ '5': 11,
+ '6': '.google.api.MonitoredResourceDescriptor',
+ '10': 'resourceDescriptors'
+ },
+ const {
+ '1': 'next_page_token',
+ '3': 2,
+ '4': 1,
+ '5': 9,
+ '10': 'nextPageToken'
+ },
+ ],
+};
+
+const ListLogsRequest$json = const {
+ '1': 'ListLogsRequest',
+ '2': const [
+ const {'1': 'parent', '3': 1, '4': 1, '5': 9, '10': 'parent'},
+ const {'1': 'page_size', '3': 2, '4': 1, '5': 5, '10': 'pageSize'},
+ const {'1': 'page_token', '3': 3, '4': 1, '5': 9, '10': 'pageToken'},
+ ],
+};
+
+const ListLogsResponse$json = const {
+ '1': 'ListLogsResponse',
+ '2': const [
+ const {'1': 'log_names', '3': 3, '4': 3, '5': 9, '10': 'logNames'},
+ const {
+ '1': 'next_page_token',
+ '3': 2,
+ '4': 1,
+ '5': 9,
+ '10': 'nextPageToken'
+ },
+ ],
+};
diff --git a/grpc/example/googleapis/lib/src/generated/google/protobuf/any.pb.dart b/grpc/example/googleapis/lib/src/generated/google/protobuf/any.pb.dart
new file mode 100644
index 0000000..15cb7be
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/protobuf/any.pb.dart
@@ -0,0 +1,54 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.protobuf_any;
+
+// ignore: UNUSED_SHOWN_NAME
+import 'dart:core' show int, bool, double, String, List, override;
+
+import 'package:protobuf/protobuf.dart';
+
+class Any extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('Any')
+ ..aOS(1, 'typeUrl')
+ ..a<List<int>>(2, 'value', PbFieldType.OY)
+ ..hasRequiredFields = false;
+
+ Any() : super();
+ Any.fromBuffer(List<int> i, [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ Any.fromJson(String i, [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ Any clone() => new Any()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static Any create() => new Any();
+ static PbList<Any> createRepeated() => new PbList<Any>();
+ static Any getDefault() {
+ if (_defaultInstance == null) _defaultInstance = new _ReadonlyAny();
+ return _defaultInstance;
+ }
+
+ static Any _defaultInstance;
+ static void $checkItem(Any v) {
+ if (v is! Any) checkItemFailed(v, 'Any');
+ }
+
+ String get typeUrl => $_getS(0, '');
+ set typeUrl(String v) {
+ $_setString(0, v);
+ }
+
+ bool hasTypeUrl() => $_has(0);
+ void clearTypeUrl() => clearField(1);
+
+ List<int> get value => $_getN(1);
+ set value(List<int> v) {
+ $_setBytes(1, v);
+ }
+
+ bool hasValue() => $_has(1);
+ void clearValue() => clearField(2);
+}
+
+class _ReadonlyAny extends Any with ReadonlyMessageMixin {}
diff --git a/grpc/example/googleapis/lib/src/generated/google/protobuf/any.pbenum.dart b/grpc/example/googleapis/lib/src/generated/google/protobuf/any.pbenum.dart
new file mode 100644
index 0000000..4db6beb
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/protobuf/any.pbenum.dart
@@ -0,0 +1,5 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.protobuf_any_pbenum;
diff --git a/grpc/example/googleapis/lib/src/generated/google/protobuf/any.pbjson.dart b/grpc/example/googleapis/lib/src/generated/google/protobuf/any.pbjson.dart
new file mode 100644
index 0000000..9784050
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/protobuf/any.pbjson.dart
@@ -0,0 +1,13 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.protobuf_any_pbjson;
+
+const Any$json = const {
+ '1': 'Any',
+ '2': const [
+ const {'1': 'type_url', '3': 1, '4': 1, '5': 9, '10': 'typeUrl'},
+ const {'1': 'value', '3': 2, '4': 1, '5': 12, '10': 'value'},
+ ],
+};
diff --git a/grpc/example/googleapis/lib/src/generated/google/protobuf/duration.pb.dart b/grpc/example/googleapis/lib/src/generated/google/protobuf/duration.pb.dart
new file mode 100644
index 0000000..79a8602
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/protobuf/duration.pb.dart
@@ -0,0 +1,56 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.protobuf_duration;
+
+// ignore: UNUSED_SHOWN_NAME
+import 'dart:core' show int, bool, double, String, List, override;
+
+import 'package:fixnum/fixnum.dart';
+import 'package:protobuf/protobuf.dart';
+
+class Duration extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('Duration')
+ ..aInt64(1, 'seconds')
+ ..a<int>(2, 'nanos', PbFieldType.O3)
+ ..hasRequiredFields = false;
+
+ Duration() : super();
+ Duration.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ Duration.fromJson(String i, [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ Duration clone() => new Duration()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static Duration create() => new Duration();
+ static PbList<Duration> createRepeated() => new PbList<Duration>();
+ static Duration getDefault() {
+ if (_defaultInstance == null) _defaultInstance = new _ReadonlyDuration();
+ return _defaultInstance;
+ }
+
+ static Duration _defaultInstance;
+ static void $checkItem(Duration v) {
+ if (v is! Duration) checkItemFailed(v, 'Duration');
+ }
+
+ Int64 get seconds => $_getI64(0);
+ set seconds(Int64 v) {
+ $_setInt64(0, v);
+ }
+
+ bool hasSeconds() => $_has(0);
+ void clearSeconds() => clearField(1);
+
+ int get nanos => $_get(1, 0);
+ set nanos(int v) {
+ $_setUnsignedInt32(1, v);
+ }
+
+ bool hasNanos() => $_has(1);
+ void clearNanos() => clearField(2);
+}
+
+class _ReadonlyDuration extends Duration with ReadonlyMessageMixin {}
diff --git a/grpc/example/googleapis/lib/src/generated/google/protobuf/duration.pbenum.dart b/grpc/example/googleapis/lib/src/generated/google/protobuf/duration.pbenum.dart
new file mode 100644
index 0000000..7a03683
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/protobuf/duration.pbenum.dart
@@ -0,0 +1,5 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.protobuf_duration_pbenum;
diff --git a/grpc/example/googleapis/lib/src/generated/google/protobuf/duration.pbjson.dart b/grpc/example/googleapis/lib/src/generated/google/protobuf/duration.pbjson.dart
new file mode 100644
index 0000000..2f816fe
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/protobuf/duration.pbjson.dart
@@ -0,0 +1,13 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.protobuf_duration_pbjson;
+
+const Duration$json = const {
+ '1': 'Duration',
+ '2': const [
+ const {'1': 'seconds', '3': 1, '4': 1, '5': 3, '10': 'seconds'},
+ const {'1': 'nanos', '3': 2, '4': 1, '5': 5, '10': 'nanos'},
+ ],
+};
diff --git a/grpc/example/googleapis/lib/src/generated/google/protobuf/empty.pb.dart b/grpc/example/googleapis/lib/src/generated/google/protobuf/empty.pb.dart
new file mode 100644
index 0000000..8dfaa6c
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/protobuf/empty.pb.dart
@@ -0,0 +1,36 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.protobuf_empty;
+
+// ignore: UNUSED_SHOWN_NAME
+import 'dart:core' show int, bool, double, String, List, override;
+
+import 'package:protobuf/protobuf.dart';
+
+class Empty extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('Empty')
+ ..hasRequiredFields = false;
+
+ Empty() : super();
+ Empty.fromBuffer(List<int> i, [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ Empty.fromJson(String i, [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ Empty clone() => new Empty()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static Empty create() => new Empty();
+ static PbList<Empty> createRepeated() => new PbList<Empty>();
+ static Empty getDefault() {
+ if (_defaultInstance == null) _defaultInstance = new _ReadonlyEmpty();
+ return _defaultInstance;
+ }
+
+ static Empty _defaultInstance;
+ static void $checkItem(Empty v) {
+ if (v is! Empty) checkItemFailed(v, 'Empty');
+ }
+}
+
+class _ReadonlyEmpty extends Empty with ReadonlyMessageMixin {}
diff --git a/grpc/example/googleapis/lib/src/generated/google/protobuf/empty.pbenum.dart b/grpc/example/googleapis/lib/src/generated/google/protobuf/empty.pbenum.dart
new file mode 100644
index 0000000..bcfc3d5
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/protobuf/empty.pbenum.dart
@@ -0,0 +1,5 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.protobuf_empty_pbenum;
diff --git a/grpc/example/googleapis/lib/src/generated/google/protobuf/empty.pbjson.dart b/grpc/example/googleapis/lib/src/generated/google/protobuf/empty.pbjson.dart
new file mode 100644
index 0000000..e372809
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/protobuf/empty.pbjson.dart
@@ -0,0 +1,9 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.protobuf_empty_pbjson;
+
+const Empty$json = const {
+ '1': 'Empty',
+};
diff --git a/grpc/example/googleapis/lib/src/generated/google/protobuf/struct.pb.dart b/grpc/example/googleapis/lib/src/generated/google/protobuf/struct.pb.dart
new file mode 100644
index 0000000..817369d
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/protobuf/struct.pb.dart
@@ -0,0 +1,208 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.protobuf_struct;
+
+// ignore: UNUSED_SHOWN_NAME
+import 'dart:core' show int, bool, double, String, List, override;
+
+import 'package:protobuf/protobuf.dart';
+
+import 'struct.pbenum.dart';
+
+export 'struct.pbenum.dart';
+
+class Struct_FieldsEntry extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('Struct_FieldsEntry')
+ ..aOS(1, 'key')
+ ..a<Value>(2, 'value', PbFieldType.OM, Value.getDefault, Value.create)
+ ..hasRequiredFields = false;
+
+ Struct_FieldsEntry() : super();
+ Struct_FieldsEntry.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ Struct_FieldsEntry.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ Struct_FieldsEntry clone() =>
+ new Struct_FieldsEntry()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static Struct_FieldsEntry create() => new Struct_FieldsEntry();
+ static PbList<Struct_FieldsEntry> createRepeated() =>
+ new PbList<Struct_FieldsEntry>();
+ static Struct_FieldsEntry getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlyStruct_FieldsEntry();
+ return _defaultInstance;
+ }
+
+ static Struct_FieldsEntry _defaultInstance;
+ static void $checkItem(Struct_FieldsEntry v) {
+ if (v is! Struct_FieldsEntry) checkItemFailed(v, 'Struct_FieldsEntry');
+ }
+
+ String get key => $_getS(0, '');
+ set key(String v) {
+ $_setString(0, v);
+ }
+
+ bool hasKey() => $_has(0);
+ void clearKey() => clearField(1);
+
+ Value get value => $_getN(1);
+ set value(Value v) {
+ setField(2, v);
+ }
+
+ bool hasValue() => $_has(1);
+ void clearValue() => clearField(2);
+}
+
+class _ReadonlyStruct_FieldsEntry extends Struct_FieldsEntry
+ with ReadonlyMessageMixin {}
+
+class Struct extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('Struct')
+ ..pp<Struct_FieldsEntry>(1, 'fields', PbFieldType.PM,
+ Struct_FieldsEntry.$checkItem, Struct_FieldsEntry.create)
+ ..hasRequiredFields = false;
+
+ Struct() : super();
+ Struct.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ Struct.fromJson(String i, [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ Struct clone() => new Struct()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static Struct create() => new Struct();
+ static PbList<Struct> createRepeated() => new PbList<Struct>();
+ static Struct getDefault() {
+ if (_defaultInstance == null) _defaultInstance = new _ReadonlyStruct();
+ return _defaultInstance;
+ }
+
+ static Struct _defaultInstance;
+ static void $checkItem(Struct v) {
+ if (v is! Struct) checkItemFailed(v, 'Struct');
+ }
+
+ List<Struct_FieldsEntry> get fields => $_getList(0);
+}
+
+class _ReadonlyStruct extends Struct with ReadonlyMessageMixin {}
+
+class Value extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('Value')
+ ..e<NullValue>(1, 'nullValue', PbFieldType.OE, NullValue.NULL_VALUE,
+ NullValue.valueOf, NullValue.values)
+ ..a<double>(2, 'numberValue', PbFieldType.OD)
+ ..aOS(3, 'stringValue')
+ ..aOB(4, 'boolValue')
+ ..a<Struct>(
+ 5, 'structValue', PbFieldType.OM, Struct.getDefault, Struct.create)
+ ..a<ListValue>(
+ 6, 'listValue', PbFieldType.OM, ListValue.getDefault, ListValue.create)
+ ..hasRequiredFields = false;
+
+ Value() : super();
+ Value.fromBuffer(List<int> i, [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ Value.fromJson(String i, [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ Value clone() => new Value()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static Value create() => new Value();
+ static PbList<Value> createRepeated() => new PbList<Value>();
+ static Value getDefault() {
+ if (_defaultInstance == null) _defaultInstance = new _ReadonlyValue();
+ return _defaultInstance;
+ }
+
+ static Value _defaultInstance;
+ static void $checkItem(Value v) {
+ if (v is! Value) checkItemFailed(v, 'Value');
+ }
+
+ NullValue get nullValue => $_getN(0);
+ set nullValue(NullValue v) {
+ setField(1, v);
+ }
+
+ bool hasNullValue() => $_has(0);
+ void clearNullValue() => clearField(1);
+
+ double get numberValue => $_getN(1);
+ set numberValue(double v) {
+ $_setDouble(1, v);
+ }
+
+ bool hasNumberValue() => $_has(1);
+ void clearNumberValue() => clearField(2);
+
+ String get stringValue => $_getS(2, '');
+ set stringValue(String v) {
+ $_setString(2, v);
+ }
+
+ bool hasStringValue() => $_has(2);
+ void clearStringValue() => clearField(3);
+
+ bool get boolValue => $_get(3, false);
+ set boolValue(bool v) {
+ $_setBool(3, v);
+ }
+
+ bool hasBoolValue() => $_has(3);
+ void clearBoolValue() => clearField(4);
+
+ Struct get structValue => $_getN(4);
+ set structValue(Struct v) {
+ setField(5, v);
+ }
+
+ bool hasStructValue() => $_has(4);
+ void clearStructValue() => clearField(5);
+
+ ListValue get listValue => $_getN(5);
+ set listValue(ListValue v) {
+ setField(6, v);
+ }
+
+ bool hasListValue() => $_has(5);
+ void clearListValue() => clearField(6);
+}
+
+class _ReadonlyValue extends Value with ReadonlyMessageMixin {}
+
+class ListValue extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('ListValue')
+ ..pp<Value>(1, 'values', PbFieldType.PM, Value.$checkItem, Value.create)
+ ..hasRequiredFields = false;
+
+ ListValue() : super();
+ ListValue.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ ListValue.fromJson(String i, [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ ListValue clone() => new ListValue()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static ListValue create() => new ListValue();
+ static PbList<ListValue> createRepeated() => new PbList<ListValue>();
+ static ListValue getDefault() {
+ if (_defaultInstance == null) _defaultInstance = new _ReadonlyListValue();
+ return _defaultInstance;
+ }
+
+ static ListValue _defaultInstance;
+ static void $checkItem(ListValue v) {
+ if (v is! ListValue) checkItemFailed(v, 'ListValue');
+ }
+
+ List<Value> get values => $_getList(0);
+}
+
+class _ReadonlyListValue extends ListValue with ReadonlyMessageMixin {}
diff --git a/grpc/example/googleapis/lib/src/generated/google/protobuf/struct.pbenum.dart b/grpc/example/googleapis/lib/src/generated/google/protobuf/struct.pbenum.dart
new file mode 100644
index 0000000..2f30d43
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/protobuf/struct.pbenum.dart
@@ -0,0 +1,25 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.protobuf_struct_pbenum;
+
+// ignore_for_file: UNDEFINED_SHOWN_NAME,UNUSED_SHOWN_NAME
+import 'dart:core' show int, dynamic, String, List, Map;
+import 'package:protobuf/protobuf.dart';
+
+class NullValue extends ProtobufEnum {
+ static const NullValue NULL_VALUE = const NullValue._(0, 'NULL_VALUE');
+
+ static const List<NullValue> values = const <NullValue>[
+ NULL_VALUE,
+ ];
+
+ static final Map<int, dynamic> _byValue = ProtobufEnum.initByValue(values);
+ static NullValue valueOf(int value) => _byValue[value] as NullValue;
+ static void $checkItem(NullValue v) {
+ if (v is! NullValue) checkItemFailed(v, 'NullValue');
+ }
+
+ const NullValue._(int v, String n) : super(v, n);
+}
diff --git a/grpc/example/googleapis/lib/src/generated/google/protobuf/struct.pbjson.dart b/grpc/example/googleapis/lib/src/generated/google/protobuf/struct.pbjson.dart
new file mode 100644
index 0000000..9afd8b7
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/protobuf/struct.pbjson.dart
@@ -0,0 +1,117 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.protobuf_struct_pbjson;
+
+const NullValue$json = const {
+ '1': 'NullValue',
+ '2': const [
+ const {'1': 'NULL_VALUE', '2': 0},
+ ],
+};
+
+const Struct$json = const {
+ '1': 'Struct',
+ '2': const [
+ const {
+ '1': 'fields',
+ '3': 1,
+ '4': 3,
+ '5': 11,
+ '6': '.google.protobuf.Struct.FieldsEntry',
+ '10': 'fields'
+ },
+ ],
+ '3': const [Struct_FieldsEntry$json],
+};
+
+const Struct_FieldsEntry$json = const {
+ '1': 'FieldsEntry',
+ '2': const [
+ const {'1': 'key', '3': 1, '4': 1, '5': 9, '10': 'key'},
+ const {
+ '1': 'value',
+ '3': 2,
+ '4': 1,
+ '5': 11,
+ '6': '.google.protobuf.Value',
+ '10': 'value'
+ },
+ ],
+ '7': const {'7': true},
+};
+
+const Value$json = const {
+ '1': 'Value',
+ '2': const [
+ const {
+ '1': 'null_value',
+ '3': 1,
+ '4': 1,
+ '5': 14,
+ '6': '.google.protobuf.NullValue',
+ '9': 0,
+ '10': 'nullValue'
+ },
+ const {
+ '1': 'number_value',
+ '3': 2,
+ '4': 1,
+ '5': 1,
+ '9': 0,
+ '10': 'numberValue'
+ },
+ const {
+ '1': 'string_value',
+ '3': 3,
+ '4': 1,
+ '5': 9,
+ '9': 0,
+ '10': 'stringValue'
+ },
+ const {
+ '1': 'bool_value',
+ '3': 4,
+ '4': 1,
+ '5': 8,
+ '9': 0,
+ '10': 'boolValue'
+ },
+ const {
+ '1': 'struct_value',
+ '3': 5,
+ '4': 1,
+ '5': 11,
+ '6': '.google.protobuf.Struct',
+ '9': 0,
+ '10': 'structValue'
+ },
+ const {
+ '1': 'list_value',
+ '3': 6,
+ '4': 1,
+ '5': 11,
+ '6': '.google.protobuf.ListValue',
+ '9': 0,
+ '10': 'listValue'
+ },
+ ],
+ '8': const [
+ const {'1': 'kind'},
+ ],
+};
+
+const ListValue$json = const {
+ '1': 'ListValue',
+ '2': const [
+ const {
+ '1': 'values',
+ '3': 1,
+ '4': 3,
+ '5': 11,
+ '6': '.google.protobuf.Value',
+ '10': 'values'
+ },
+ ],
+};
diff --git a/grpc/example/googleapis/lib/src/generated/google/protobuf/timestamp.pb.dart b/grpc/example/googleapis/lib/src/generated/google/protobuf/timestamp.pb.dart
new file mode 100644
index 0000000..49c181f
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/protobuf/timestamp.pb.dart
@@ -0,0 +1,56 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.protobuf_timestamp;
+
+// ignore: UNUSED_SHOWN_NAME
+import 'dart:core' show int, bool, double, String, List, override;
+
+import 'package:fixnum/fixnum.dart';
+import 'package:protobuf/protobuf.dart';
+
+class Timestamp extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('Timestamp')
+ ..aInt64(1, 'seconds')
+ ..a<int>(2, 'nanos', PbFieldType.O3)
+ ..hasRequiredFields = false;
+
+ Timestamp() : super();
+ Timestamp.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ Timestamp.fromJson(String i, [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ Timestamp clone() => new Timestamp()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static Timestamp create() => new Timestamp();
+ static PbList<Timestamp> createRepeated() => new PbList<Timestamp>();
+ static Timestamp getDefault() {
+ if (_defaultInstance == null) _defaultInstance = new _ReadonlyTimestamp();
+ return _defaultInstance;
+ }
+
+ static Timestamp _defaultInstance;
+ static void $checkItem(Timestamp v) {
+ if (v is! Timestamp) checkItemFailed(v, 'Timestamp');
+ }
+
+ Int64 get seconds => $_getI64(0);
+ set seconds(Int64 v) {
+ $_setInt64(0, v);
+ }
+
+ bool hasSeconds() => $_has(0);
+ void clearSeconds() => clearField(1);
+
+ int get nanos => $_get(1, 0);
+ set nanos(int v) {
+ $_setUnsignedInt32(1, v);
+ }
+
+ bool hasNanos() => $_has(1);
+ void clearNanos() => clearField(2);
+}
+
+class _ReadonlyTimestamp extends Timestamp with ReadonlyMessageMixin {}
diff --git a/grpc/example/googleapis/lib/src/generated/google/protobuf/timestamp.pbenum.dart b/grpc/example/googleapis/lib/src/generated/google/protobuf/timestamp.pbenum.dart
new file mode 100644
index 0000000..9c1ae44
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/protobuf/timestamp.pbenum.dart
@@ -0,0 +1,5 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.protobuf_timestamp_pbenum;
diff --git a/grpc/example/googleapis/lib/src/generated/google/protobuf/timestamp.pbjson.dart b/grpc/example/googleapis/lib/src/generated/google/protobuf/timestamp.pbjson.dart
new file mode 100644
index 0000000..b6cf119
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/protobuf/timestamp.pbjson.dart
@@ -0,0 +1,13 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.protobuf_timestamp_pbjson;
+
+const Timestamp$json = const {
+ '1': 'Timestamp',
+ '2': const [
+ const {'1': 'seconds', '3': 1, '4': 1, '5': 3, '10': 'seconds'},
+ const {'1': 'nanos', '3': 2, '4': 1, '5': 5, '10': 'nanos'},
+ ],
+};
diff --git a/grpc/example/googleapis/lib/src/generated/google/rpc/status.pb.dart b/grpc/example/googleapis/lib/src/generated/google/rpc/status.pb.dart
new file mode 100644
index 0000000..31e034f
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/rpc/status.pb.dart
@@ -0,0 +1,61 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.rpc_status;
+
+// ignore: UNUSED_SHOWN_NAME
+import 'dart:core' show int, bool, double, String, List, override;
+
+import 'package:protobuf/protobuf.dart';
+
+import '../protobuf/any.pb.dart' as $google$protobuf;
+
+class Status extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('Status')
+ ..a<int>(1, 'code', PbFieldType.O3)
+ ..aOS(2, 'message')
+ ..pp<$google$protobuf.Any>(3, 'details', PbFieldType.PM,
+ $google$protobuf.Any.$checkItem, $google$protobuf.Any.create)
+ ..hasRequiredFields = false;
+
+ Status() : super();
+ Status.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ Status.fromJson(String i, [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ Status clone() => new Status()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static Status create() => new Status();
+ static PbList<Status> createRepeated() => new PbList<Status>();
+ static Status getDefault() {
+ if (_defaultInstance == null) _defaultInstance = new _ReadonlyStatus();
+ return _defaultInstance;
+ }
+
+ static Status _defaultInstance;
+ static void $checkItem(Status v) {
+ if (v is! Status) checkItemFailed(v, 'Status');
+ }
+
+ int get code => $_get(0, 0);
+ set code(int v) {
+ $_setUnsignedInt32(0, v);
+ }
+
+ bool hasCode() => $_has(0);
+ void clearCode() => clearField(1);
+
+ String get message => $_getS(1, '');
+ set message(String v) {
+ $_setString(1, v);
+ }
+
+ bool hasMessage() => $_has(1);
+ void clearMessage() => clearField(2);
+
+ List<$google$protobuf.Any> get details => $_getList(2);
+}
+
+class _ReadonlyStatus extends Status with ReadonlyMessageMixin {}
diff --git a/grpc/example/googleapis/lib/src/generated/google/rpc/status.pbenum.dart b/grpc/example/googleapis/lib/src/generated/google/rpc/status.pbenum.dart
new file mode 100644
index 0000000..7bd9759
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/rpc/status.pbenum.dart
@@ -0,0 +1,5 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.rpc_status_pbenum;
diff --git a/grpc/example/googleapis/lib/src/generated/google/rpc/status.pbjson.dart b/grpc/example/googleapis/lib/src/generated/google/rpc/status.pbjson.dart
new file mode 100644
index 0000000..279074d
--- /dev/null
+++ b/grpc/example/googleapis/lib/src/generated/google/rpc/status.pbjson.dart
@@ -0,0 +1,21 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library google.rpc_status_pbjson;
+
+const Status$json = const {
+ '1': 'Status',
+ '2': const [
+ const {'1': 'code', '3': 1, '4': 1, '5': 5, '10': 'code'},
+ const {'1': 'message', '3': 2, '4': 1, '5': 9, '10': 'message'},
+ const {
+ '1': 'details',
+ '3': 3,
+ '4': 3,
+ '5': 11,
+ '6': '.google.protobuf.Any',
+ '10': 'details'
+ },
+ ],
+};
diff --git a/grpc/example/googleapis/pubspec.yaml b/grpc/example/googleapis/pubspec.yaml
new file mode 100644
index 0000000..3620b1c
--- /dev/null
+++ b/grpc/example/googleapis/pubspec.yaml
@@ -0,0 +1,15 @@
+name: googleapis
+description: Dart gRPC client sample for Google APIs
+homepage: https://github.com/dart-lang/grpc-dart
+
+environment:
+ sdk: '>=2.0.0 <3.0.0'
+
+dependencies:
+ async: '>=1.13.3 <3.0.0'
+ grpc:
+ path: ../../
+ protobuf: ^0.10.1
+
+dev_dependencies:
+ test: ^1.3.0
diff --git a/grpc/example/googleapis/tool/regenerate.sh b/grpc/example/googleapis/tool/regenerate.sh
new file mode 100755
index 0000000..6584746
--- /dev/null
+++ b/grpc/example/googleapis/tool/regenerate.sh
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+
+if [ ! -d "$PROTOBUF" ]; then
+ echo "Please set the PROTOBUF environment variable to your clone of google/protobuf."
+ exit -1
+fi
+
+if [ ! -d "$GOOGLEAPIS" ]; then
+ echo "Please set the GOOGLEAPIS environment variable to your clone of googleapis/googleapis."
+ exit -1
+fi
+
+PROTOC="protoc --dart_out=grpc:lib/src/generated -I$PROTOBUF/src -I$GOOGLEAPIS"
+
+$PROTOC $GOOGLEAPIS/google/logging/v2/logging.proto
+$PROTOC $GOOGLEAPIS/google/logging/v2/log_entry.proto
+$PROTOC $GOOGLEAPIS/google/logging/type/log_severity.proto
+$PROTOC $GOOGLEAPIS/google/logging/type/http_request.proto
+
+$PROTOC $GOOGLEAPIS/google/api/monitored_resource.proto
+$PROTOC $GOOGLEAPIS/google/api/label.proto
+
+$PROTOC $GOOGLEAPIS/google/rpc/status.proto
+
+$PROTOC $PROTOBUF/src/google/protobuf/any.proto
+$PROTOC $PROTOBUF/src/google/protobuf/duration.proto
+$PROTOC $PROTOBUF/src/google/protobuf/empty.proto
+$PROTOC $PROTOBUF/src/google/protobuf/struct.proto
+$PROTOC $PROTOBUF/src/google/protobuf/timestamp.proto
+
+dartfmt -w lib/src/generated
diff --git a/grpc/example/helloworld/README.md b/grpc/example/helloworld/README.md
new file mode 100644
index 0000000..aeb4317
--- /dev/null
+++ b/grpc/example/helloworld/README.md
@@ -0,0 +1,50 @@
+# Description
+The hello world server and client demonstrate how to use Dart gRPC libraries to
+perform unary RPCs.
+
+See the definition of the hello world service in `protos/helloworld.proto`.
+
+# Run the sample code
+To compile and run the example, assuming you are in the root of the helloworld
+folder, i.e., .../example/helloworld/, first get the dependencies by running:
+
+```sh
+$ pub get
+```
+
+Then, to run the server:
+
+```sh
+$ dart bin/server.dart
+```
+
+Likewise, to run the client:
+
+```sh
+$ dart bin/client.dart
+```
+
+# Regenerate the stubs
+
+If you have made changes to the message or service definition in
+`protos/helloworld.proto` and need to regenerate the corresponding Dart files,
+you will need to have protoc version 3.0.0 or higher and the Dart protoc plugin
+version 0.7.9 or higher on your PATH.
+
+To install protoc, see the instructions on
+[the Protocol Buffers website](https://developers.google.com/protocol-buffers/).
+
+The easiest way to get the Dart protoc plugin is by running
+
+```sh
+$ pub global activate protoc_plugin
+```
+
+and follow the directions to add `~/.pub-cache/bin` to your PATH, if you haven't
+already done so.
+
+You can now regenerate the Dart files by running
+
+```sh
+$ protoc --dart_out=grpc:lib/src/generated -Iprotos protos/helloworld.proto
+```
diff --git a/grpc/example/helloworld/bin/client.dart b/grpc/example/helloworld/bin/client.dart
new file mode 100644
index 0000000..6f44dbf
--- /dev/null
+++ b/grpc/example/helloworld/bin/client.dart
@@ -0,0 +1,40 @@
+// Copyright (c) 2018, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/// Dart implementation of the gRPC helloworld.Greeter client.
+import 'dart:async';
+
+import 'package:grpc/grpc.dart';
+
+import 'package:helloworld/src/generated/helloworld.pb.dart';
+import 'package:helloworld/src/generated/helloworld.pbgrpc.dart';
+
+Future<void> main(List<String> args) async {
+ final channel = new ClientChannel('localhost',
+ port: 50051,
+ options: const ChannelOptions(
+ credentials: const ChannelCredentials.insecure()));
+ final stub = new GreeterClient(channel);
+
+ final name = args.isNotEmpty ? args[0] : 'world';
+
+ try {
+ final response = await stub.sayHello(new HelloRequest()..name = name);
+ print('Greeter client received: ${response.message}');
+ } catch (e) {
+ print('Caught error: $e');
+ }
+ await channel.shutdown();
+}
diff --git a/grpc/example/helloworld/bin/server.dart b/grpc/example/helloworld/bin/server.dart
new file mode 100644
index 0000000..cfed15d
--- /dev/null
+++ b/grpc/example/helloworld/bin/server.dart
@@ -0,0 +1,35 @@
+// Copyright (c) 2018, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/// Dart implementation of the gRPC helloworld.Greeter server.
+import 'dart:async';
+
+import 'package:grpc/grpc.dart';
+
+import 'package:helloworld/src/generated/helloworld.pb.dart';
+import 'package:helloworld/src/generated/helloworld.pbgrpc.dart';
+
+class GreeterService extends GreeterServiceBase {
+ @override
+ Future<HelloReply> sayHello(ServiceCall call, HelloRequest request) async {
+ return new HelloReply()..message = 'Hello, ${request.name}!';
+ }
+}
+
+Future<void> main(List<String> args) async {
+ final server = new Server([new GreeterService()]);
+ await server.serve(port: 50051);
+ print('Server listening on port ${server.port}...');
+}
diff --git a/grpc/example/helloworld/lib/src/generated/helloworld.pb.dart b/grpc/example/helloworld/lib/src/generated/helloworld.pb.dart
new file mode 100644
index 0000000..d441d8c
--- /dev/null
+++ b/grpc/example/helloworld/lib/src/generated/helloworld.pb.dart
@@ -0,0 +1,84 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library helloworld_helloworld;
+
+// ignore: UNUSED_SHOWN_NAME
+import 'dart:core' show int, bool, double, String, List, override;
+
+import 'package:protobuf/protobuf.dart';
+
+class HelloRequest extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('HelloRequest')
+ ..aOS(1, 'name')
+ ..hasRequiredFields = false;
+
+ HelloRequest() : super();
+ HelloRequest.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ HelloRequest.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ HelloRequest clone() => new HelloRequest()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static HelloRequest create() => new HelloRequest();
+ static PbList<HelloRequest> createRepeated() => new PbList<HelloRequest>();
+ static HelloRequest getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlyHelloRequest();
+ return _defaultInstance;
+ }
+
+ static HelloRequest _defaultInstance;
+ static void $checkItem(HelloRequest v) {
+ if (v is! HelloRequest) checkItemFailed(v, 'HelloRequest');
+ }
+
+ String get name => $_getS(0, '');
+ set name(String v) {
+ $_setString(0, v);
+ }
+
+ bool hasName() => $_has(0);
+ void clearName() => clearField(1);
+}
+
+class _ReadonlyHelloRequest extends HelloRequest with ReadonlyMessageMixin {}
+
+class HelloReply extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('HelloReply')
+ ..aOS(1, 'message')
+ ..hasRequiredFields = false;
+
+ HelloReply() : super();
+ HelloReply.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ HelloReply.fromJson(String i, [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ HelloReply clone() => new HelloReply()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static HelloReply create() => new HelloReply();
+ static PbList<HelloReply> createRepeated() => new PbList<HelloReply>();
+ static HelloReply getDefault() {
+ if (_defaultInstance == null) _defaultInstance = new _ReadonlyHelloReply();
+ return _defaultInstance;
+ }
+
+ static HelloReply _defaultInstance;
+ static void $checkItem(HelloReply v) {
+ if (v is! HelloReply) checkItemFailed(v, 'HelloReply');
+ }
+
+ String get message => $_getS(0, '');
+ set message(String v) {
+ $_setString(0, v);
+ }
+
+ bool hasMessage() => $_has(0);
+ void clearMessage() => clearField(1);
+}
+
+class _ReadonlyHelloReply extends HelloReply with ReadonlyMessageMixin {}
diff --git a/grpc/example/helloworld/lib/src/generated/helloworld.pbenum.dart b/grpc/example/helloworld/lib/src/generated/helloworld.pbenum.dart
new file mode 100644
index 0000000..167efb8
--- /dev/null
+++ b/grpc/example/helloworld/lib/src/generated/helloworld.pbenum.dart
@@ -0,0 +1,5 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library helloworld_helloworld_pbenum;
diff --git a/grpc/example/helloworld/lib/src/generated/helloworld.pbgrpc.dart b/grpc/example/helloworld/lib/src/generated/helloworld.pbgrpc.dart
new file mode 100644
index 0000000..c390483
--- /dev/null
+++ b/grpc/example/helloworld/lib/src/generated/helloworld.pbgrpc.dart
@@ -0,0 +1,49 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library helloworld_helloworld_pbgrpc;
+
+import 'dart:async';
+
+import 'package:grpc/grpc.dart';
+
+import 'helloworld.pb.dart';
+export 'helloworld.pb.dart';
+
+class GreeterClient extends Client {
+ static final _$sayHello = new ClientMethod<HelloRequest, HelloReply>(
+ '/helloworld.Greeter/SayHello',
+ (HelloRequest value) => value.writeToBuffer(),
+ (List<int> value) => new HelloReply.fromBuffer(value));
+
+ GreeterClient(ClientChannel channel, {CallOptions options})
+ : super(channel, options: options);
+
+ ResponseFuture<HelloReply> sayHello(HelloRequest request,
+ {CallOptions options}) {
+ final call = $createCall(_$sayHello, new Stream.fromIterable([request]),
+ options: options);
+ return new ResponseFuture(call);
+ }
+}
+
+abstract class GreeterServiceBase extends Service {
+ String get $name => 'helloworld.Greeter';
+
+ GreeterServiceBase() {
+ $addMethod(new ServiceMethod<HelloRequest, HelloReply>(
+ 'SayHello',
+ sayHello_Pre,
+ false,
+ false,
+ (List<int> value) => new HelloRequest.fromBuffer(value),
+ (HelloReply value) => value.writeToBuffer()));
+ }
+
+ Future<HelloReply> sayHello_Pre(ServiceCall call, Future request) async {
+ return sayHello(call, await request);
+ }
+
+ Future<HelloReply> sayHello(ServiceCall call, HelloRequest request);
+}
diff --git a/grpc/example/helloworld/lib/src/generated/helloworld.pbjson.dart b/grpc/example/helloworld/lib/src/generated/helloworld.pbjson.dart
new file mode 100644
index 0000000..ea6a1a4
--- /dev/null
+++ b/grpc/example/helloworld/lib/src/generated/helloworld.pbjson.dart
@@ -0,0 +1,19 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library helloworld_helloworld_pbjson;
+
+const HelloRequest$json = const {
+ '1': 'HelloRequest',
+ '2': const [
+ const {'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'},
+ ],
+};
+
+const HelloReply$json = const {
+ '1': 'HelloReply',
+ '2': const [
+ const {'1': 'message', '3': 1, '4': 1, '5': 9, '10': 'message'},
+ ],
+};
diff --git a/grpc/example/helloworld/protos/helloworld.proto b/grpc/example/helloworld/protos/helloworld.proto
new file mode 100644
index 0000000..be878ce
--- /dev/null
+++ b/grpc/example/helloworld/protos/helloworld.proto
@@ -0,0 +1,38 @@
+// Copyright 2015 gRPC authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+syntax = "proto3";
+
+option java_multiple_files = true;
+option java_package = "io.grpc.examples.helloworld";
+option java_outer_classname = "HelloWorldProto";
+option objc_class_prefix = "HLW";
+
+package helloworld;
+
+// The greeting service definition.
+service Greeter {
+ // Sends a greeting
+ rpc SayHello (HelloRequest) returns (HelloReply) {}
+}
+
+// The request message containing the user's name.
+message HelloRequest {
+ string name = 1;
+}
+
+// The response message containing the greetings
+message HelloReply {
+ string message = 1;
+}
diff --git a/grpc/example/helloworld/pubspec.yaml b/grpc/example/helloworld/pubspec.yaml
new file mode 100644
index 0000000..9f14b4a
--- /dev/null
+++ b/grpc/example/helloworld/pubspec.yaml
@@ -0,0 +1,15 @@
+name: helloworld
+description: Dart gRPC sample client and server.
+homepage: https://github.com/dart-lang/grpc-dart
+
+environment:
+ sdk: '>=2.0.0 <3.0.0'
+
+dependencies:
+ async: '>=1.13.3 <3.0.0'
+ grpc:
+ path: ../../
+ protobuf: ^0.10.1
+
+dev_dependencies:
+ test: ^1.3.0
diff --git a/grpc/example/metadata/README.md b/grpc/example/metadata/README.md
new file mode 100644
index 0000000..947605c
--- /dev/null
+++ b/grpc/example/metadata/README.md
@@ -0,0 +1,25 @@
+# Description
+The metadata server and client demonstrate how to handle custom metadata,
+cancellation, and timeouts in Dart gRPC.
+
+See the definition of the metadata service in `protos/metadata.proto`.
+
+# Run the sample code
+To compile and run the example, assuming you are in the root of the metadata
+folder, i.e., .../example/metadata/, first get the dependencies by running:
+
+```sh
+$ pub get
+```
+
+Then, to run the server:
+
+```sh
+$ dart bin/server.dart
+```
+
+Likewise, to run the client:
+
+```sh
+$ dart bin/client.dart
+```
diff --git a/grpc/example/metadata/bin/client.dart b/grpc/example/metadata/bin/client.dart
new file mode 100644
index 0000000..f2febf5
--- /dev/null
+++ b/grpc/example/metadata/bin/client.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'package:metadata/src/client.dart';
+
+main(List<String> args) {
+ new Client().main(args);
+}
diff --git a/grpc/example/metadata/bin/server.dart b/grpc/example/metadata/bin/server.dart
new file mode 100644
index 0000000..0eb9b7d
--- /dev/null
+++ b/grpc/example/metadata/bin/server.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'package:metadata/src/server.dart';
+
+main(List<String> args) {
+ new Server().main(args);
+}
diff --git a/grpc/example/metadata/lib/src/client.dart b/grpc/example/metadata/lib/src/client.dart
new file mode 100644
index 0000000..7e3b79b
--- /dev/null
+++ b/grpc/example/metadata/lib/src/client.dart
@@ -0,0 +1,149 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:async';
+
+import 'package:grpc/grpc.dart';
+
+import 'generated/metadata.pbgrpc.dart';
+
+class Client {
+ ClientChannel channel;
+ MetadataClient stub;
+
+ Future<void> main(List<String> args) async {
+ channel = new ClientChannel('127.0.0.1',
+ port: 8080,
+ options: const ChannelOptions(
+ credentials: const ChannelCredentials.insecure()));
+ stub = new MetadataClient(channel);
+ // Run all of the demos in order.
+ await runEcho();
+ await runEchoDelayCancel();
+ await runAddOneCancel();
+ await runFibonacciCancel();
+ await runFibonacciTimeout();
+ await channel.shutdown();
+ }
+
+ /// Run the echo demo.
+ ///
+ /// Send custom metadata with a RPC, and print out the received response and
+ /// metadata.
+ Future<void> runEcho() async {
+ final request = new Record()..value = 'Kaj';
+ final call = stub.echo(request,
+ options: new CallOptions(metadata: {'peer': 'Verner'}));
+ call.headers.then((headers) {
+ print('Received header metadata: $headers');
+ });
+ call.trailers.then((trailers) {
+ print('Received trailer metadata: $trailers');
+ });
+ final response = await call;
+ print('Echo response: ${response.value}');
+ }
+
+ /// Run the echo with delay cancel demo.
+ ///
+ /// Same as the echo demo, but demonstrating per-client custom metadata, as
+ /// well as a per-call metadata. The server will delay the response for the
+ /// requested duration, during which the client will cancel the RPC.
+ Future<void> runEchoDelayCancel() async {
+ final stubWithCustomOptions = new MetadataClient(channel,
+ options: new CallOptions(metadata: {'peer': 'Verner'}));
+ final request = new Record()..value = 'Kaj';
+ final call = stubWithCustomOptions.echo(request,
+ options: new CallOptions(metadata: {'delay': '1'}));
+ call.headers.then((headers) {
+ print('Received header metadata: $headers');
+ });
+ call.trailers.then((trailers) {
+ print('Received trailer metadata: $trailers');
+ });
+ await new Future.delayed(new Duration(milliseconds: 10));
+ call.cancel();
+ try {
+ final response = await call;
+ print('Unexpected echo response: ${response.value}');
+ } catch (error) {
+ print('Expected error: $error');
+ }
+ }
+
+ /// Run the addOne cancel demo.
+ ///
+ /// Makes a bi-directional RPC, sends 4 requests, and cancels the RPC after
+ /// receiving 3 responses.
+ Future<void> runAddOneCancel() async {
+ final numbers = new StreamController<int>();
+ final call =
+ stub.addOne(numbers.stream.map((value) => new Number()..value = value));
+ final receivedThree = new Completer<bool>();
+ final sub = call.listen((number) {
+ print('AddOneCancel: Received ${number.value}');
+ if (number.value == 3) {
+ receivedThree.complete(true);
+ }
+ }, onError: (e) => print('Caught: $e'));
+ numbers.add(1);
+ numbers.add(2);
+ numbers.add(3);
+ numbers.add(4);
+ await receivedThree.future;
+ await call.cancel();
+ await Future.wait([sub.cancel(), numbers.close()]);
+ }
+
+ /// Run the Fibonacci demo.
+ ///
+ /// Call an RPC that returns a stream of Fibonacci numbers. Cancel the call
+ /// after receiving more than 5 responses.
+ Future<void> runFibonacciCancel() async {
+ final call = stub.fibonacci(new Empty());
+ int count = 0;
+ try {
+ await for (var number in call) {
+ count++;
+ print('Received ${number.value} (count=$count)');
+ if (count > 5) {
+ await call.cancel();
+ }
+ }
+ } on GrpcError catch (e) {
+ print('Caught: $e');
+ }
+ print('Final count: $count');
+ }
+
+ /// Run the timeout demo.
+ ///
+ /// Call an RPC that returns a stream of Fibonacci numbers, and specify an RPC
+ /// timeout of 2 seconds.
+ Future<void> runFibonacciTimeout() async {
+ final call = stub.fibonacci(new Empty(),
+ options: new CallOptions(timeout: new Duration(seconds: 2)));
+ int count = 0;
+ try {
+ await for (var number in call) {
+ count++;
+ print('Received ${number.value} (count=$count)');
+ }
+ } on GrpcError catch (e) {
+ print('Caught: $e');
+ }
+ print('Final count: $count');
+ }
+}
diff --git a/grpc/example/metadata/lib/src/generated/metadata.pb.dart b/grpc/example/metadata/lib/src/generated/metadata.pb.dart
new file mode 100644
index 0000000..f5390b5
--- /dev/null
+++ b/grpc/example/metadata/lib/src/generated/metadata.pb.dart
@@ -0,0 +1,108 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library grpc_metadata;
+
+// ignore: UNUSED_SHOWN_NAME
+import 'dart:core' show int, bool, double, String, List, override;
+
+import 'package:protobuf/protobuf.dart';
+
+class Record extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('Record')
+ ..aOS(1, 'value')
+ ..hasRequiredFields = false;
+
+ Record() : super();
+ Record.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ Record.fromJson(String i, [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ Record clone() => new Record()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static Record create() => new Record();
+ static PbList<Record> createRepeated() => new PbList<Record>();
+ static Record getDefault() {
+ if (_defaultInstance == null) _defaultInstance = new _ReadonlyRecord();
+ return _defaultInstance;
+ }
+
+ static Record _defaultInstance;
+ static void $checkItem(Record v) {
+ if (v is! Record) checkItemFailed(v, 'Record');
+ }
+
+ String get value => $_getS(0, '');
+ set value(String v) {
+ $_setString(0, v);
+ }
+
+ bool hasValue() => $_has(0);
+ void clearValue() => clearField(1);
+}
+
+class _ReadonlyRecord extends Record with ReadonlyMessageMixin {}
+
+class Number extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('Number')
+ ..a<int>(1, 'value', PbFieldType.O3)
+ ..hasRequiredFields = false;
+
+ Number() : super();
+ Number.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ Number.fromJson(String i, [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ Number clone() => new Number()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static Number create() => new Number();
+ static PbList<Number> createRepeated() => new PbList<Number>();
+ static Number getDefault() {
+ if (_defaultInstance == null) _defaultInstance = new _ReadonlyNumber();
+ return _defaultInstance;
+ }
+
+ static Number _defaultInstance;
+ static void $checkItem(Number v) {
+ if (v is! Number) checkItemFailed(v, 'Number');
+ }
+
+ int get value => $_get(0, 0);
+ set value(int v) {
+ $_setUnsignedInt32(0, v);
+ }
+
+ bool hasValue() => $_has(0);
+ void clearValue() => clearField(1);
+}
+
+class _ReadonlyNumber extends Number with ReadonlyMessageMixin {}
+
+class Empty extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('Empty')
+ ..hasRequiredFields = false;
+
+ Empty() : super();
+ Empty.fromBuffer(List<int> i, [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ Empty.fromJson(String i, [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ Empty clone() => new Empty()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static Empty create() => new Empty();
+ static PbList<Empty> createRepeated() => new PbList<Empty>();
+ static Empty getDefault() {
+ if (_defaultInstance == null) _defaultInstance = new _ReadonlyEmpty();
+ return _defaultInstance;
+ }
+
+ static Empty _defaultInstance;
+ static void $checkItem(Empty v) {
+ if (v is! Empty) checkItemFailed(v, 'Empty');
+ }
+}
+
+class _ReadonlyEmpty extends Empty with ReadonlyMessageMixin {}
diff --git a/grpc/example/metadata/lib/src/generated/metadata.pbgrpc.dart b/grpc/example/metadata/lib/src/generated/metadata.pbgrpc.dart
new file mode 100644
index 0000000..e7de680
--- /dev/null
+++ b/grpc/example/metadata/lib/src/generated/metadata.pbgrpc.dart
@@ -0,0 +1,87 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library grpc_metadata_pbgrpc;
+
+import 'dart:async';
+
+import 'package:grpc/grpc.dart';
+
+import 'metadata.pb.dart';
+export 'metadata.pb.dart';
+
+class MetadataClient extends Client {
+ static final _$echo = new ClientMethod<Record, Record>(
+ '/grpc.Metadata/Echo',
+ (Record value) => value.writeToBuffer(),
+ (List<int> value) => new Record.fromBuffer(value));
+ static final _$addOne = new ClientMethod<Number, Number>(
+ '/grpc.Metadata/AddOne',
+ (Number value) => value.writeToBuffer(),
+ (List<int> value) => new Number.fromBuffer(value));
+ static final _$fibonacci = new ClientMethod<Empty, Number>(
+ '/grpc.Metadata/Fibonacci',
+ (Empty value) => value.writeToBuffer(),
+ (List<int> value) => new Number.fromBuffer(value));
+
+ MetadataClient(ClientChannel channel, {CallOptions options})
+ : super(channel, options: options);
+
+ ResponseFuture<Record> echo(Record request, {CallOptions options}) {
+ final call = $createCall(_$echo, new Stream.fromIterable([request]),
+ options: options);
+ return new ResponseFuture(call);
+ }
+
+ ResponseStream<Number> addOne(Stream<Number> request, {CallOptions options}) {
+ final call = $createCall(_$addOne, request, options: options);
+ return new ResponseStream(call);
+ }
+
+ ResponseStream<Number> fibonacci(Empty request, {CallOptions options}) {
+ final call = $createCall(_$fibonacci, new Stream.fromIterable([request]),
+ options: options);
+ return new ResponseStream(call);
+ }
+}
+
+abstract class MetadataServiceBase extends Service {
+ String get $name => 'grpc.Metadata';
+
+ MetadataServiceBase() {
+ $addMethod(new ServiceMethod<Record, Record>(
+ 'Echo',
+ echo_Pre,
+ false,
+ false,
+ (List<int> value) => new Record.fromBuffer(value),
+ (Record value) => value.writeToBuffer()));
+ $addMethod(new ServiceMethod<Number, Number>(
+ 'AddOne',
+ addOne,
+ true,
+ true,
+ (List<int> value) => new Number.fromBuffer(value),
+ (Number value) => value.writeToBuffer()));
+ $addMethod(new ServiceMethod<Empty, Number>(
+ 'Fibonacci',
+ fibonacci_Pre,
+ false,
+ true,
+ (List<int> value) => new Empty.fromBuffer(value),
+ (Number value) => value.writeToBuffer()));
+ }
+
+ Future<Record> echo_Pre(ServiceCall call, Future request) async {
+ return echo(call, await request);
+ }
+
+ Stream<Number> fibonacci_Pre(ServiceCall call, Future request) async* {
+ yield* fibonacci(call, (await request) as Empty);
+ }
+
+ Future<Record> echo(ServiceCall call, Record request);
+ Stream<Number> addOne(ServiceCall call, Stream<Number> request);
+ Stream<Number> fibonacci(ServiceCall call, Empty request);
+}
diff --git a/grpc/example/metadata/lib/src/server.dart b/grpc/example/metadata/lib/src/server.dart
new file mode 100644
index 0000000..96344c9
--- /dev/null
+++ b/grpc/example/metadata/lib/src/server.dart
@@ -0,0 +1,84 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:async';
+
+import 'package:grpc/grpc.dart' as grpc;
+
+import 'generated/metadata.pbgrpc.dart';
+
+class MetadataService extends MetadataServiceBase {
+ int callCount = 0;
+
+ @override
+ Future<Record> echo(grpc.ServiceCall call, Record request) async {
+ final peer = call.clientMetadata['peer'];
+ final count = callCount++;
+ print('Echo: Call #$count: Peer: $peer, request: ${request.value}');
+ call.headers['count'] = '${count}';
+ call.trailers['hello'] = request.value;
+
+ final delay = call.clientMetadata['delay'];
+ if (delay != null) {
+ await new Future.delayed(new Duration(seconds: int.parse(delay)));
+ }
+
+ return new Record()..value = peer;
+ }
+
+ @override
+ Stream<Number> addOne(grpc.ServiceCall call, Stream<Number> request) async* {
+ int lastNumber = -1;
+ try {
+ await for (var number in request) {
+ lastNumber = number.value;
+ yield new Number()..value = number.value + 1;
+ }
+ } catch (error) {
+ print('Caught: $error, last number = $lastNumber');
+ } finally {
+ if (call.isCanceled) {
+ print('AddOne: Call canceled');
+ }
+ }
+ }
+
+ /// Streams a Fibonacci number every 500ms until the call is canceled.
+ Stream<Number> fibonacci(grpc.ServiceCall call, Empty request) async* {
+ int previous = 0;
+ int current = 1;
+ try {
+ while (true) {
+ await new Future.delayed(new Duration(milliseconds: 500));
+ yield new Number()..value = current;
+ final next = current + previous;
+ previous = current;
+ current = next;
+ }
+ } finally {
+ if (call.isCanceled) {
+ print('Fibonacci: Canceled.');
+ }
+ }
+ }
+}
+
+class Server {
+ Future<void> main(List<String> args) async {
+ final server = new grpc.Server([new MetadataService()]);
+ await server.serve(port: 8080);
+ print('Server listening on port ${server.port}...');
+ }
+}
diff --git a/grpc/example/metadata/protos/metadata.proto b/grpc/example/metadata/protos/metadata.proto
new file mode 100644
index 0000000..9b6a63f
--- /dev/null
+++ b/grpc/example/metadata/protos/metadata.proto
@@ -0,0 +1,67 @@
+// Copyright 2017, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package grpc;
+
+// Interface exported by the server.
+service Metadata {
+ // Echo metadata.
+ //
+ // Echoes the given input as trailer metadata. Sets a call counter as header
+ // metadata, and returns the value of the 'hello' key in the client metadata
+ // as the result.
+ rpc Echo(Record) returns (Record) {}
+
+ // Adds 1 to the numbers in the request stream.
+ //
+ // Uses bidirectional streaming.
+ rpc AddOne(stream Number) returns (stream Number) {}
+
+ // Fibonacci.
+ //
+ // Streams Fibonacci numbers until the call is canceled or times out.
+ rpc Fibonacci(Empty) returns (stream Number) {}
+}
+
+// A message containing a single string value.
+message Record {
+ string value = 1;
+}
+
+// A message containing a single number.
+message Number {
+ int32 value = 1;
+}
+
+// A message containing nothing.
+message Empty {
+}
+
diff --git a/grpc/example/metadata/pubspec.yaml b/grpc/example/metadata/pubspec.yaml
new file mode 100644
index 0000000..7dfd33c
--- /dev/null
+++ b/grpc/example/metadata/pubspec.yaml
@@ -0,0 +1,15 @@
+name: metadata
+description: Dart gRPC sample client and server.
+homepage: https://github.com/dart-lang/grpc-dart
+
+environment:
+ sdk: '>=2.0.0 <3.0.0'
+
+dependencies:
+ async: '>=1.13.3 <3.0.0'
+ grpc:
+ path: ../../
+ protobuf: ^0.10.1
+
+dev_dependencies:
+ test: ^1.3.0
diff --git a/grpc/example/metadata/tool/regenerate.sh b/grpc/example/metadata/tool/regenerate.sh
new file mode 100755
index 0000000..65e2a27
--- /dev/null
+++ b/grpc/example/metadata/tool/regenerate.sh
@@ -0,0 +1,4 @@
+#!/usr/bin/env bash
+protoc --dart_out=grpc:lib/src/generated -Iprotos protos/metadata.proto
+rm lib/src/generated/metadata.pb{enum,json}.dart
+dartfmt -w lib/src/generated
diff --git a/grpc/example/route_guide/README.md b/grpc/example/route_guide/README.md
new file mode 100644
index 0000000..ece2692
--- /dev/null
+++ b/grpc/example/route_guide/README.md
@@ -0,0 +1,53 @@
+# Description
+The route guide server and client demonstrate how to use Dart gRPC libraries to
+perform unary, client streaming, server streaming and full duplex RPCs.
+
+See the definition of the route guide service in `protos/route_guide.proto`.
+
+# Run the sample code
+To compile and run the example, assuming you are in the root of the route_guide
+folder, i.e., .../example/route_guide/, first get the dependencies by running:
+
+```sh
+$ pub get
+```
+
+Then, to run the server:
+
+```sh
+$ dart bin/server.dart
+```
+
+Likewise, to run the client:
+
+```sh
+$ dart bin/client.dart
+```
+
+# Regenerate the stubs
+
+If you have made changes to the message or service definition in
+`protos/route_guide.proto` and need to regenerate the corresponding Dart files,
+you will need to have protoc version 3.0.0 or higher and the Dart protoc plugin
+version 0.7.9 or higher on your PATH.
+
+To install protoc with Dart support, take these steps:
+
+1. Install the `protoc` matching your development operating system from
+[the Protocol Buffers releases page](https://github.com/google/protobuf/releases)
+(e.g. `protoc-3.5.1-osx-x86_64.zip` for macOS).
+
+1. Get the Dart protoc plugin by running
+
+ ```sh
+ $ pub global activate protoc_plugin
+ ```
+
+1. Add `~/.pub-cache/bin` to your PATH, if you haven't
+already done so.
+
+You can now regenerate the Dart files by running
+
+```sh
+$ protoc --dart_out=grpc:lib/src/generated -Iprotos protos/route_guide.proto
+```
diff --git a/grpc/example/route_guide/bin/client.dart b/grpc/example/route_guide/bin/client.dart
new file mode 100644
index 0000000..662666e
--- /dev/null
+++ b/grpc/example/route_guide/bin/client.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'package:route_guide/src/client.dart';
+
+main(List<String> args) async {
+ await new Client().main(args);
+}
diff --git a/grpc/example/route_guide/bin/server.dart b/grpc/example/route_guide/bin/server.dart
new file mode 100644
index 0000000..fada4af
--- /dev/null
+++ b/grpc/example/route_guide/bin/server.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'package:route_guide/src/server.dart';
+
+main(List<String> args) async {
+ await new Server().main(args);
+}
diff --git a/grpc/example/route_guide/data/route_guide_db.json b/grpc/example/route_guide/data/route_guide_db.json
new file mode 100644
index 0000000..9d6a980
--- /dev/null
+++ b/grpc/example/route_guide/data/route_guide_db.json
@@ -0,0 +1,601 @@
+[{
+ "location": {
+ "latitude": 407838351,
+ "longitude": -746143763
+ },
+ "name": "Patriots Path, Mendham, NJ 07945, USA"
+}, {
+ "location": {
+ "latitude": 408122808,
+ "longitude": -743999179
+ },
+ "name": "101 New Jersey 10, Whippany, NJ 07981, USA"
+}, {
+ "location": {
+ "latitude": 413628156,
+ "longitude": -749015468
+ },
+ "name": "U.S. 6, Shohola, PA 18458, USA"
+}, {
+ "location": {
+ "latitude": 419999544,
+ "longitude": -740371136
+ },
+ "name": "5 Conners Road, Kingston, NY 12401, USA"
+}, {
+ "location": {
+ "latitude": 414008389,
+ "longitude": -743951297
+ },
+ "name": "Mid Hudson Psychiatric Center, New Hampton, NY 10958, USA"
+}, {
+ "location": {
+ "latitude": 419611318,
+ "longitude": -746524769
+ },
+ "name": "287 Flugertown Road, Livingston Manor, NY 12758, USA"
+}, {
+ "location": {
+ "latitude": 406109563,
+ "longitude": -742186778
+ },
+ "name": "4001 Tremley Point Road, Linden, NJ 07036, USA"
+}, {
+ "location": {
+ "latitude": 416802456,
+ "longitude": -742370183
+ },
+ "name": "352 South Mountain Road, Wallkill, NY 12589, USA"
+}, {
+ "location": {
+ "latitude": 412950425,
+ "longitude": -741077389
+ },
+ "name": "Bailey Turn Road, Harriman, NY 10926, USA"
+}, {
+ "location": {
+ "latitude": 412144655,
+ "longitude": -743949739
+ },
+ "name": "193-199 Wawayanda Road, Hewitt, NJ 07421, USA"
+}, {
+ "location": {
+ "latitude": 415736605,
+ "longitude": -742847522
+ },
+ "name": "406-496 Ward Avenue, Pine Bush, NY 12566, USA"
+}, {
+ "location": {
+ "latitude": 413843930,
+ "longitude": -740501726
+ },
+ "name": "162 Merrill Road, Highland Mills, NY 10930, USA"
+}, {
+ "location": {
+ "latitude": 410873075,
+ "longitude": -744459023
+ },
+ "name": "Clinton Road, West Milford, NJ 07480, USA"
+}, {
+ "location": {
+ "latitude": 412346009,
+ "longitude": -744026814
+ },
+ "name": "16 Old Brook Lane, Warwick, NY 10990, USA"
+}, {
+ "location": {
+ "latitude": 402948455,
+ "longitude": -747903913
+ },
+ "name": "3 Drake Lane, Pennington, NJ 08534, USA"
+}, {
+ "location": {
+ "latitude": 406337092,
+ "longitude": -740122226
+ },
+ "name": "6324 8th Avenue, Brooklyn, NY 11220, USA"
+}, {
+ "location": {
+ "latitude": 406421967,
+ "longitude": -747727624
+ },
+ "name": "1 Merck Access Road, Whitehouse Station, NJ 08889, USA"
+}, {
+ "location": {
+ "latitude": 416318082,
+ "longitude": -749677716
+ },
+ "name": "78-98 Schalck Road, Narrowsburg, NY 12764, USA"
+}, {
+ "location": {
+ "latitude": 415301720,
+ "longitude": -748416257
+ },
+ "name": "282 Lakeview Drive Road, Highland Lake, NY 12743, USA"
+}, {
+ "location": {
+ "latitude": 402647019,
+ "longitude": -747071791
+ },
+ "name": "330 Evelyn Avenue, Hamilton Township, NJ 08619, USA"
+}, {
+ "location": {
+ "latitude": 412567807,
+ "longitude": -741058078
+ },
+ "name": "New York State Reference Route 987E, Southfields, NY 10975, USA"
+}, {
+ "location": {
+ "latitude": 416855156,
+ "longitude": -744420597
+ },
+ "name": "103-271 Tempaloni Road, Ellenville, NY 12428, USA"
+}, {
+ "location": {
+ "latitude": 404663628,
+ "longitude": -744820157
+ },
+ "name": "1300 Airport Road, North Brunswick Township, NJ 08902, USA"
+}, {
+ "location": {
+ "latitude": 407113723,
+ "longitude": -749746483
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 402133926,
+ "longitude": -743613249
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 400273442,
+ "longitude": -741220915
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 411236786,
+ "longitude": -744070769
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 411633782,
+ "longitude": -746784970
+ },
+ "name": "211-225 Plains Road, Augusta, NJ 07822, USA"
+}, {
+ "location": {
+ "latitude": 415830701,
+ "longitude": -742952812
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 413447164,
+ "longitude": -748712898
+ },
+ "name": "165 Pedersen Ridge Road, Milford, PA 18337, USA"
+}, {
+ "location": {
+ "latitude": 405047245,
+ "longitude": -749800722
+ },
+ "name": "100-122 Locktown Road, Frenchtown, NJ 08825, USA"
+}, {
+ "location": {
+ "latitude": 418858923,
+ "longitude": -746156790
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 417951888,
+ "longitude": -748484944
+ },
+ "name": "650-652 Willi Hill Road, Swan Lake, NY 12783, USA"
+}, {
+ "location": {
+ "latitude": 407033786,
+ "longitude": -743977337
+ },
+ "name": "26 East 3rd Street, New Providence, NJ 07974, USA"
+}, {
+ "location": {
+ "latitude": 417548014,
+ "longitude": -740075041
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 410395868,
+ "longitude": -744972325
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 404615353,
+ "longitude": -745129803
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 406589790,
+ "longitude": -743560121
+ },
+ "name": "611 Lawrence Avenue, Westfield, NJ 07090, USA"
+}, {
+ "location": {
+ "latitude": 414653148,
+ "longitude": -740477477
+ },
+ "name": "18 Lannis Avenue, New Windsor, NY 12553, USA"
+}, {
+ "location": {
+ "latitude": 405957808,
+ "longitude": -743255336
+ },
+ "name": "82-104 Amherst Avenue, Colonia, NJ 07067, USA"
+}, {
+ "location": {
+ "latitude": 411733589,
+ "longitude": -741648093
+ },
+ "name": "170 Seven Lakes Drive, Sloatsburg, NY 10974, USA"
+}, {
+ "location": {
+ "latitude": 412676291,
+ "longitude": -742606606
+ },
+ "name": "1270 Lakes Road, Monroe, NY 10950, USA"
+}, {
+ "location": {
+ "latitude": 409224445,
+ "longitude": -748286738
+ },
+ "name": "509-535 Alphano Road, Great Meadows, NJ 07838, USA"
+}, {
+ "location": {
+ "latitude": 406523420,
+ "longitude": -742135517
+ },
+ "name": "652 Garden Street, Elizabeth, NJ 07202, USA"
+}, {
+ "location": {
+ "latitude": 401827388,
+ "longitude": -740294537
+ },
+ "name": "349 Sea Spray Court, Neptune City, NJ 07753, USA"
+}, {
+ "location": {
+ "latitude": 410564152,
+ "longitude": -743685054
+ },
+ "name": "13-17 Stanley Street, West Milford, NJ 07480, USA"
+}, {
+ "location": {
+ "latitude": 408472324,
+ "longitude": -740726046
+ },
+ "name": "47 Industrial Avenue, Teterboro, NJ 07608, USA"
+}, {
+ "location": {
+ "latitude": 412452168,
+ "longitude": -740214052
+ },
+ "name": "5 White Oak Lane, Stony Point, NY 10980, USA"
+}, {
+ "location": {
+ "latitude": 409146138,
+ "longitude": -746188906
+ },
+ "name": "Berkshire Valley Management Area Trail, Jefferson, NJ, USA"
+}, {
+ "location": {
+ "latitude": 404701380,
+ "longitude": -744781745
+ },
+ "name": "1007 Jersey Avenue, New Brunswick, NJ 08901, USA"
+}, {
+ "location": {
+ "latitude": 409642566,
+ "longitude": -746017679
+ },
+ "name": "6 East Emerald Isle Drive, Lake Hopatcong, NJ 07849, USA"
+}, {
+ "location": {
+ "latitude": 408031728,
+ "longitude": -748645385
+ },
+ "name": "1358-1474 New Jersey 57, Port Murray, NJ 07865, USA"
+}, {
+ "location": {
+ "latitude": 413700272,
+ "longitude": -742135189
+ },
+ "name": "367 Prospect Road, Chester, NY 10918, USA"
+}, {
+ "location": {
+ "latitude": 404310607,
+ "longitude": -740282632
+ },
+ "name": "10 Simon Lake Drive, Atlantic Highlands, NJ 07716, USA"
+}, {
+ "location": {
+ "latitude": 409319800,
+ "longitude": -746201391
+ },
+ "name": "11 Ward Street, Mount Arlington, NJ 07856, USA"
+}, {
+ "location": {
+ "latitude": 406685311,
+ "longitude": -742108603
+ },
+ "name": "300-398 Jefferson Avenue, Elizabeth, NJ 07201, USA"
+}, {
+ "location": {
+ "latitude": 419018117,
+ "longitude": -749142781
+ },
+ "name": "43 Dreher Road, Roscoe, NY 12776, USA"
+}, {
+ "location": {
+ "latitude": 412856162,
+ "longitude": -745148837
+ },
+ "name": "Swan Street, Pine Island, NY 10969, USA"
+}, {
+ "location": {
+ "latitude": 416560744,
+ "longitude": -746721964
+ },
+ "name": "66 Pleasantview Avenue, Monticello, NY 12701, USA"
+}, {
+ "location": {
+ "latitude": 405314270,
+ "longitude": -749836354
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 414219548,
+ "longitude": -743327440
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 415534177,
+ "longitude": -742900616
+ },
+ "name": "565 Winding Hills Road, Montgomery, NY 12549, USA"
+}, {
+ "location": {
+ "latitude": 406898530,
+ "longitude": -749127080
+ },
+ "name": "231 Rocky Run Road, Glen Gardner, NJ 08826, USA"
+}, {
+ "location": {
+ "latitude": 407586880,
+ "longitude": -741670168
+ },
+ "name": "100 Mount Pleasant Avenue, Newark, NJ 07104, USA"
+}, {
+ "location": {
+ "latitude": 400106455,
+ "longitude": -742870190
+ },
+ "name": "517-521 Huntington Drive, Manchester Township, NJ 08759, USA"
+}, {
+ "location": {
+ "latitude": 400066188,
+ "longitude": -746793294
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 418803880,
+ "longitude": -744102673
+ },
+ "name": "40 Mountain Road, Napanoch, NY 12458, USA"
+}, {
+ "location": {
+ "latitude": 414204288,
+ "longitude": -747895140
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 414777405,
+ "longitude": -740615601
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 415464475,
+ "longitude": -747175374
+ },
+ "name": "48 North Road, Forestburgh, NY 12777, USA"
+}, {
+ "location": {
+ "latitude": 404062378,
+ "longitude": -746376177
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 405688272,
+ "longitude": -749285130
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 400342070,
+ "longitude": -748788996
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 401809022,
+ "longitude": -744157964
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 404226644,
+ "longitude": -740517141
+ },
+ "name": "9 Thompson Avenue, Leonardo, NJ 07737, USA"
+}, {
+ "location": {
+ "latitude": 410322033,
+ "longitude": -747871659
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 407100674,
+ "longitude": -747742727
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 418811433,
+ "longitude": -741718005
+ },
+ "name": "213 Bush Road, Stone Ridge, NY 12484, USA"
+}, {
+ "location": {
+ "latitude": 415034302,
+ "longitude": -743850945
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 411349992,
+ "longitude": -743694161
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 404839914,
+ "longitude": -744759616
+ },
+ "name": "1-17 Bergen Court, New Brunswick, NJ 08901, USA"
+}, {
+ "location": {
+ "latitude": 414638017,
+ "longitude": -745957854
+ },
+ "name": "35 Oakland Valley Road, Cuddebackville, NY 12729, USA"
+}, {
+ "location": {
+ "latitude": 412127800,
+ "longitude": -740173578
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 401263460,
+ "longitude": -747964303
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 412843391,
+ "longitude": -749086026
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 418512773,
+ "longitude": -743067823
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 404318328,
+ "longitude": -740835638
+ },
+ "name": "42-102 Main Street, Belford, NJ 07718, USA"
+}, {
+ "location": {
+ "latitude": 419020746,
+ "longitude": -741172328
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 404080723,
+ "longitude": -746119569
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 401012643,
+ "longitude": -744035134
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 404306372,
+ "longitude": -741079661
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 403966326,
+ "longitude": -748519297
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 405002031,
+ "longitude": -748407866
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 409532885,
+ "longitude": -742200683
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 416851321,
+ "longitude": -742674555
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 406411633,
+ "longitude": -741722051
+ },
+ "name": "3387 Richmond Terrace, Staten Island, NY 10303, USA"
+}, {
+ "location": {
+ "latitude": 413069058,
+ "longitude": -744597778
+ },
+ "name": "261 Van Sickle Road, Goshen, NY 10924, USA"
+}, {
+ "location": {
+ "latitude": 418465462,
+ "longitude": -746859398
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 411733222,
+ "longitude": -744228360
+ },
+ "name": ""
+}, {
+ "location": {
+ "latitude": 410248224,
+ "longitude": -747127767
+ },
+ "name": "3 Hasta Way, Newton, NJ 07860, USA"
+}]
diff --git a/grpc/example/route_guide/lib/src/client.dart b/grpc/example/route_guide/lib/src/client.dart
new file mode 100644
index 0000000..b893779
--- /dev/null
+++ b/grpc/example/route_guide/lib/src/client.dart
@@ -0,0 +1,152 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:async';
+import 'dart:math' show Random;
+
+import 'package:grpc/grpc.dart';
+
+import 'common.dart';
+import 'generated/route_guide.pb.dart';
+import 'generated/route_guide.pbgrpc.dart';
+
+class Client {
+ ClientChannel channel;
+ RouteGuideClient stub;
+
+ Future<void> main(List<String> args) async {
+ channel = new ClientChannel('127.0.0.1',
+ port: 8080,
+ options: const ChannelOptions(
+ credentials: const ChannelCredentials.insecure()));
+ stub = new RouteGuideClient(channel,
+ options: new CallOptions(timeout: new Duration(seconds: 30)));
+ // Run all of the demos in order.
+ try {
+ await runGetFeature();
+ await runListFeatures();
+ await runRecordRoute();
+ await runRouteChat();
+ } catch (e) {
+ print('Caught error: $e');
+ }
+ await channel.shutdown();
+ }
+
+ void printFeature(Feature feature) {
+ final latitude = feature.location.latitude;
+ final longitude = feature.location.longitude;
+ final name = feature.name.isEmpty
+ ? 'no feature'
+ : 'feature called "${feature.name}"';
+ print(
+ 'Found $name at ${latitude / coordFactor}, ${longitude / coordFactor}');
+ }
+
+ /// Run the getFeature demo. Calls getFeature with a point known to have a
+ /// feature and a point known not to have a feature.
+ Future<void> runGetFeature() async {
+ final point1 = new Point()
+ ..latitude = 409146138
+ ..longitude = -746188906;
+ final point2 = new Point()
+ ..latitude = 0
+ ..longitude = 0;
+
+ printFeature(await stub.getFeature(point1));
+ printFeature(await stub.getFeature(point2));
+ }
+
+ /// Run the listFeatures demo. Calls listFeatures with a rectangle containing
+ /// all of the features in the pre-generated database. Prints each response as
+ /// it comes in.
+ Future<void> runListFeatures() async {
+ final lo = new Point()
+ ..latitude = 400000000
+ ..longitude = -750000000;
+ final hi = new Point()
+ ..latitude = 420000000
+ ..longitude = -730000000;
+ final rect = new Rectangle()
+ ..lo = lo
+ ..hi = hi;
+
+ print('Looking for features between 40, -75 and 42, -73');
+ await for (var feature in stub.listFeatures(rect)) {
+ printFeature(feature);
+ }
+ }
+
+ /// Run the recordRoute demo. Sends several randomly chosen points from the
+ /// pre-generated feature database with a variable delay in between. Prints
+ /// the statistics when they are sent from the server.
+ Future<void> runRecordRoute() async {
+ Stream<Point> generateRoute(int count) async* {
+ final random = new Random();
+
+ for (int i = 0; i < count; i++) {
+ final point = featuresDb[random.nextInt(featuresDb.length)].location;
+ print(
+ 'Visiting point ${point.latitude / coordFactor}, ${point.longitude /
+ coordFactor}');
+ yield point;
+ await new Future.delayed(
+ new Duration(milliseconds: 200 + random.nextInt(100)));
+ }
+ }
+
+ final summary = await stub.recordRoute(generateRoute(10));
+ print('Finished trip with ${summary.pointCount} points');
+ print('Passed ${summary.featureCount} features');
+ print('Travelled ${summary.distance} meters');
+ print('It took ${summary.elapsedTime} seconds');
+ }
+
+ /// Run the routeChat demo. Send some chat messages, and print any chat
+ /// messages that are sent from the server.
+ Future<void> runRouteChat() async {
+ RouteNote createNote(String message, int latitude, int longitude) {
+ final location = new Point()
+ ..latitude = latitude
+ ..longitude = longitude;
+ return new RouteNote()
+ ..message = message
+ ..location = location;
+ }
+
+ final notes = <RouteNote>[
+ createNote('First message', 0, 0),
+ createNote('Second message', 0, 1),
+ createNote('Third message', 1, 0),
+ createNote('Fourth message', 0, 0),
+ ];
+
+ Stream<RouteNote> outgoingNotes() async* {
+ for (final note in notes) {
+ // Short delay to simulate some other interaction.
+ await new Future.delayed(new Duration(milliseconds: 10));
+ print('Sending message ${note.message} at ${note.location.latitude}, '
+ '${note.location.longitude}');
+ yield note;
+ }
+ }
+
+ final call = stub.routeChat(outgoingNotes());
+ await for (var note in call) {
+ print('Got message ${note.message} at ${note.location.latitude}, ${note
+ .location.longitude}');
+ }
+ }
+}
diff --git a/grpc/example/route_guide/lib/src/common.dart b/grpc/example/route_guide/lib/src/common.dart
new file mode 100644
index 0000000..f7b2992
--- /dev/null
+++ b/grpc/example/route_guide/lib/src/common.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:convert';
+import 'dart:io';
+
+import 'generated/route_guide.pb.dart';
+
+const coordFactor = 1e7;
+
+final List<Feature> featuresDb = _readDatabase();
+
+List<Feature> _readDatabase() {
+ final dbData = new File('data/route_guide_db.json').readAsStringSync();
+ final List db = jsonDecode(dbData);
+ return db.map((entry) {
+ final location = new Point()
+ ..latitude = entry['location']['latitude']
+ ..longitude = entry['location']['longitude'];
+ return new Feature()
+ ..name = entry['name']
+ ..location = location;
+ }).toList();
+}
diff --git a/grpc/example/route_guide/lib/src/generated/route_guide.pb.dart b/grpc/example/route_guide/lib/src/generated/route_guide.pb.dart
new file mode 100644
index 0000000..842adf4
--- /dev/null
+++ b/grpc/example/route_guide/lib/src/generated/route_guide.pb.dart
@@ -0,0 +1,254 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library routeguide_route_guide;
+
+// ignore: UNUSED_SHOWN_NAME
+import 'dart:core' show int, bool, double, String, List, override;
+
+import 'package:protobuf/protobuf.dart';
+
+class Point extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('Point')
+ ..a<int>(1, 'latitude', PbFieldType.O3)
+ ..a<int>(2, 'longitude', PbFieldType.O3)
+ ..hasRequiredFields = false;
+
+ Point() : super();
+ Point.fromBuffer(List<int> i, [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ Point.fromJson(String i, [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ Point clone() => new Point()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static Point create() => new Point();
+ static PbList<Point> createRepeated() => new PbList<Point>();
+ static Point getDefault() {
+ if (_defaultInstance == null) _defaultInstance = new _ReadonlyPoint();
+ return _defaultInstance;
+ }
+
+ static Point _defaultInstance;
+ static void $checkItem(Point v) {
+ if (v is! Point) checkItemFailed(v, 'Point');
+ }
+
+ int get latitude => $_get(0, 0);
+ set latitude(int v) {
+ $_setUnsignedInt32(0, v);
+ }
+
+ bool hasLatitude() => $_has(0);
+ void clearLatitude() => clearField(1);
+
+ int get longitude => $_get(1, 0);
+ set longitude(int v) {
+ $_setUnsignedInt32(1, v);
+ }
+
+ bool hasLongitude() => $_has(1);
+ void clearLongitude() => clearField(2);
+}
+
+class _ReadonlyPoint extends Point with ReadonlyMessageMixin {}
+
+class Rectangle extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('Rectangle')
+ ..a<Point>(1, 'lo', PbFieldType.OM, Point.getDefault, Point.create)
+ ..a<Point>(2, 'hi', PbFieldType.OM, Point.getDefault, Point.create)
+ ..hasRequiredFields = false;
+
+ Rectangle() : super();
+ Rectangle.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ Rectangle.fromJson(String i, [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ Rectangle clone() => new Rectangle()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static Rectangle create() => new Rectangle();
+ static PbList<Rectangle> createRepeated() => new PbList<Rectangle>();
+ static Rectangle getDefault() {
+ if (_defaultInstance == null) _defaultInstance = new _ReadonlyRectangle();
+ return _defaultInstance;
+ }
+
+ static Rectangle _defaultInstance;
+ static void $checkItem(Rectangle v) {
+ if (v is! Rectangle) checkItemFailed(v, 'Rectangle');
+ }
+
+ Point get lo => $_getN(0);
+ set lo(Point v) {
+ setField(1, v);
+ }
+
+ bool hasLo() => $_has(0);
+ void clearLo() => clearField(1);
+
+ Point get hi => $_getN(1);
+ set hi(Point v) {
+ setField(2, v);
+ }
+
+ bool hasHi() => $_has(1);
+ void clearHi() => clearField(2);
+}
+
+class _ReadonlyRectangle extends Rectangle with ReadonlyMessageMixin {}
+
+class Feature extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('Feature')
+ ..aOS(1, 'name')
+ ..a<Point>(2, 'location', PbFieldType.OM, Point.getDefault, Point.create)
+ ..hasRequiredFields = false;
+
+ Feature() : super();
+ Feature.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ Feature.fromJson(String i, [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ Feature clone() => new Feature()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static Feature create() => new Feature();
+ static PbList<Feature> createRepeated() => new PbList<Feature>();
+ static Feature getDefault() {
+ if (_defaultInstance == null) _defaultInstance = new _ReadonlyFeature();
+ return _defaultInstance;
+ }
+
+ static Feature _defaultInstance;
+ static void $checkItem(Feature v) {
+ if (v is! Feature) checkItemFailed(v, 'Feature');
+ }
+
+ String get name => $_getS(0, '');
+ set name(String v) {
+ $_setString(0, v);
+ }
+
+ bool hasName() => $_has(0);
+ void clearName() => clearField(1);
+
+ Point get location => $_getN(1);
+ set location(Point v) {
+ setField(2, v);
+ }
+
+ bool hasLocation() => $_has(1);
+ void clearLocation() => clearField(2);
+}
+
+class _ReadonlyFeature extends Feature with ReadonlyMessageMixin {}
+
+class RouteNote extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('RouteNote')
+ ..a<Point>(1, 'location', PbFieldType.OM, Point.getDefault, Point.create)
+ ..aOS(2, 'message')
+ ..hasRequiredFields = false;
+
+ RouteNote() : super();
+ RouteNote.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ RouteNote.fromJson(String i, [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ RouteNote clone() => new RouteNote()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static RouteNote create() => new RouteNote();
+ static PbList<RouteNote> createRepeated() => new PbList<RouteNote>();
+ static RouteNote getDefault() {
+ if (_defaultInstance == null) _defaultInstance = new _ReadonlyRouteNote();
+ return _defaultInstance;
+ }
+
+ static RouteNote _defaultInstance;
+ static void $checkItem(RouteNote v) {
+ if (v is! RouteNote) checkItemFailed(v, 'RouteNote');
+ }
+
+ Point get location => $_getN(0);
+ set location(Point v) {
+ setField(1, v);
+ }
+
+ bool hasLocation() => $_has(0);
+ void clearLocation() => clearField(1);
+
+ String get message => $_getS(1, '');
+ set message(String v) {
+ $_setString(1, v);
+ }
+
+ bool hasMessage() => $_has(1);
+ void clearMessage() => clearField(2);
+}
+
+class _ReadonlyRouteNote extends RouteNote with ReadonlyMessageMixin {}
+
+class RouteSummary extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('RouteSummary')
+ ..a<int>(1, 'pointCount', PbFieldType.O3)
+ ..a<int>(2, 'featureCount', PbFieldType.O3)
+ ..a<int>(3, 'distance', PbFieldType.O3)
+ ..a<int>(4, 'elapsedTime', PbFieldType.O3)
+ ..hasRequiredFields = false;
+
+ RouteSummary() : super();
+ RouteSummary.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ RouteSummary.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ RouteSummary clone() => new RouteSummary()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static RouteSummary create() => new RouteSummary();
+ static PbList<RouteSummary> createRepeated() => new PbList<RouteSummary>();
+ static RouteSummary getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlyRouteSummary();
+ return _defaultInstance;
+ }
+
+ static RouteSummary _defaultInstance;
+ static void $checkItem(RouteSummary v) {
+ if (v is! RouteSummary) checkItemFailed(v, 'RouteSummary');
+ }
+
+ int get pointCount => $_get(0, 0);
+ set pointCount(int v) {
+ $_setUnsignedInt32(0, v);
+ }
+
+ bool hasPointCount() => $_has(0);
+ void clearPointCount() => clearField(1);
+
+ int get featureCount => $_get(1, 0);
+ set featureCount(int v) {
+ $_setUnsignedInt32(1, v);
+ }
+
+ bool hasFeatureCount() => $_has(1);
+ void clearFeatureCount() => clearField(2);
+
+ int get distance => $_get(2, 0);
+ set distance(int v) {
+ $_setUnsignedInt32(2, v);
+ }
+
+ bool hasDistance() => $_has(2);
+ void clearDistance() => clearField(3);
+
+ int get elapsedTime => $_get(3, 0);
+ set elapsedTime(int v) {
+ $_setUnsignedInt32(3, v);
+ }
+
+ bool hasElapsedTime() => $_has(3);
+ void clearElapsedTime() => clearField(4);
+}
+
+class _ReadonlyRouteSummary extends RouteSummary with ReadonlyMessageMixin {}
diff --git a/grpc/example/route_guide/lib/src/generated/route_guide.pbenum.dart b/grpc/example/route_guide/lib/src/generated/route_guide.pbenum.dart
new file mode 100644
index 0000000..11b7aee
--- /dev/null
+++ b/grpc/example/route_guide/lib/src/generated/route_guide.pbenum.dart
@@ -0,0 +1,5 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library routeguide_route_guide_pbenum;
diff --git a/grpc/example/route_guide/lib/src/generated/route_guide.pbgrpc.dart b/grpc/example/route_guide/lib/src/generated/route_guide.pbgrpc.dart
new file mode 100644
index 0000000..c0826b4
--- /dev/null
+++ b/grpc/example/route_guide/lib/src/generated/route_guide.pbgrpc.dart
@@ -0,0 +1,107 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library routeguide_route_guide_pbgrpc;
+
+import 'dart:async';
+
+import 'package:grpc/grpc.dart';
+
+import 'route_guide.pb.dart';
+export 'route_guide.pb.dart';
+
+class RouteGuideClient extends Client {
+ static final _$getFeature = new ClientMethod<Point, Feature>(
+ '/routeguide.RouteGuide/GetFeature',
+ (Point value) => value.writeToBuffer(),
+ (List<int> value) => new Feature.fromBuffer(value));
+ static final _$listFeatures = new ClientMethod<Rectangle, Feature>(
+ '/routeguide.RouteGuide/ListFeatures',
+ (Rectangle value) => value.writeToBuffer(),
+ (List<int> value) => new Feature.fromBuffer(value));
+ static final _$recordRoute = new ClientMethod<Point, RouteSummary>(
+ '/routeguide.RouteGuide/RecordRoute',
+ (Point value) => value.writeToBuffer(),
+ (List<int> value) => new RouteSummary.fromBuffer(value));
+ static final _$routeChat = new ClientMethod<RouteNote, RouteNote>(
+ '/routeguide.RouteGuide/RouteChat',
+ (RouteNote value) => value.writeToBuffer(),
+ (List<int> value) => new RouteNote.fromBuffer(value));
+
+ RouteGuideClient(ClientChannel channel, {CallOptions options})
+ : super(channel, options: options);
+
+ ResponseFuture<Feature> getFeature(Point request, {CallOptions options}) {
+ final call = $createCall(_$getFeature, new Stream.fromIterable([request]),
+ options: options);
+ return new ResponseFuture(call);
+ }
+
+ ResponseStream<Feature> listFeatures(Rectangle request,
+ {CallOptions options}) {
+ final call = $createCall(_$listFeatures, new Stream.fromIterable([request]),
+ options: options);
+ return new ResponseStream(call);
+ }
+
+ ResponseFuture<RouteSummary> recordRoute(Stream<Point> request,
+ {CallOptions options}) {
+ final call = $createCall(_$recordRoute, request, options: options);
+ return new ResponseFuture(call);
+ }
+
+ ResponseStream<RouteNote> routeChat(Stream<RouteNote> request,
+ {CallOptions options}) {
+ final call = $createCall(_$routeChat, request, options: options);
+ return new ResponseStream(call);
+ }
+}
+
+abstract class RouteGuideServiceBase extends Service {
+ String get $name => 'routeguide.RouteGuide';
+
+ RouteGuideServiceBase() {
+ $addMethod(new ServiceMethod<Point, Feature>(
+ 'GetFeature',
+ getFeature_Pre,
+ false,
+ false,
+ (List<int> value) => new Point.fromBuffer(value),
+ (Feature value) => value.writeToBuffer()));
+ $addMethod(new ServiceMethod<Rectangle, Feature>(
+ 'ListFeatures',
+ listFeatures_Pre,
+ false,
+ true,
+ (List<int> value) => new Rectangle.fromBuffer(value),
+ (Feature value) => value.writeToBuffer()));
+ $addMethod(new ServiceMethod<Point, RouteSummary>(
+ 'RecordRoute',
+ recordRoute,
+ true,
+ false,
+ (List<int> value) => new Point.fromBuffer(value),
+ (RouteSummary value) => value.writeToBuffer()));
+ $addMethod(new ServiceMethod<RouteNote, RouteNote>(
+ 'RouteChat',
+ routeChat,
+ true,
+ true,
+ (List<int> value) => new RouteNote.fromBuffer(value),
+ (RouteNote value) => value.writeToBuffer()));
+ }
+
+ Future<Feature> getFeature_Pre(ServiceCall call, Future request) async {
+ return getFeature(call, await request);
+ }
+
+ Stream<Feature> listFeatures_Pre(ServiceCall call, Future request) async* {
+ yield* listFeatures(call, (await request) as Rectangle);
+ }
+
+ Future<Feature> getFeature(ServiceCall call, Point request);
+ Stream<Feature> listFeatures(ServiceCall call, Rectangle request);
+ Future<RouteSummary> recordRoute(ServiceCall call, Stream<Point> request);
+ Stream<RouteNote> routeChat(ServiceCall call, Stream<RouteNote> request);
+}
diff --git a/grpc/example/route_guide/lib/src/generated/route_guide.pbjson.dart b/grpc/example/route_guide/lib/src/generated/route_guide.pbjson.dart
new file mode 100644
index 0000000..6b93028
--- /dev/null
+++ b/grpc/example/route_guide/lib/src/generated/route_guide.pbjson.dart
@@ -0,0 +1,75 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library routeguide_route_guide_pbjson;
+
+const Point$json = const {
+ '1': 'Point',
+ '2': const [
+ const {'1': 'latitude', '3': 1, '4': 1, '5': 5, '10': 'latitude'},
+ const {'1': 'longitude', '3': 2, '4': 1, '5': 5, '10': 'longitude'},
+ ],
+};
+
+const Rectangle$json = const {
+ '1': 'Rectangle',
+ '2': const [
+ const {
+ '1': 'lo',
+ '3': 1,
+ '4': 1,
+ '5': 11,
+ '6': '.routeguide.Point',
+ '10': 'lo'
+ },
+ const {
+ '1': 'hi',
+ '3': 2,
+ '4': 1,
+ '5': 11,
+ '6': '.routeguide.Point',
+ '10': 'hi'
+ },
+ ],
+};
+
+const Feature$json = const {
+ '1': 'Feature',
+ '2': const [
+ const {'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'},
+ const {
+ '1': 'location',
+ '3': 2,
+ '4': 1,
+ '5': 11,
+ '6': '.routeguide.Point',
+ '10': 'location'
+ },
+ ],
+};
+
+const RouteNote$json = const {
+ '1': 'RouteNote',
+ '2': const [
+ const {
+ '1': 'location',
+ '3': 1,
+ '4': 1,
+ '5': 11,
+ '6': '.routeguide.Point',
+ '10': 'location'
+ },
+ const {'1': 'message', '3': 2, '4': 1, '5': 9, '10': 'message'},
+ ],
+};
+
+const RouteSummary$json = const {
+ '1': 'RouteSummary',
+ '2': const [
+ const {'1': 'point_count', '3': 1, '4': 1, '5': 5, '10': 'pointCount'},
+ const {'1': 'feature_count', '3': 2, '4': 1, '5': 5, '10': 'featureCount'},
+ const {'1': 'distance', '3': 3, '4': 1, '5': 5, '10': 'distance'},
+ const {'1': 'elapsed_time', '3': 4, '4': 1, '5': 5, '10': 'elapsedTime'},
+ ],
+};
diff --git a/grpc/example/route_guide/lib/src/server.dart b/grpc/example/route_guide/lib/src/server.dart
new file mode 100644
index 0000000..a8e6676
--- /dev/null
+++ b/grpc/example/route_guide/lib/src/server.dart
@@ -0,0 +1,150 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:async';
+import 'dart:math' show atan2, cos, max, min, pi, sin, sqrt;
+
+import 'package:grpc/grpc.dart' as grpc;
+
+import 'common.dart';
+import 'generated/route_guide.pb.dart';
+import 'generated/route_guide.pbgrpc.dart';
+
+class RouteGuideService extends RouteGuideServiceBase {
+ final routeNotes = <Point, List<RouteNote>>{};
+
+ /// GetFeature handler. Returns a feature for the given location.
+ /// The [context] object provides access to client metadata, cancellation, etc.
+ @override
+ Future<Feature> getFeature(grpc.ServiceCall call, Point request) async {
+ return featuresDb.firstWhere((f) => f.location == request,
+ orElse: () => new Feature()..location = request);
+ }
+
+ Rectangle _normalize(Rectangle r) {
+ final lo = new Point()
+ ..latitude = min(r.lo.latitude, r.hi.latitude)
+ ..longitude = min(r.lo.longitude, r.hi.longitude);
+
+ final hi = new Point()
+ ..latitude = max(r.lo.latitude, r.hi.latitude)
+ ..longitude = max(r.lo.longitude, r.hi.longitude);
+
+ return new Rectangle()
+ ..lo = lo
+ ..hi = hi;
+ }
+
+ bool _contains(Rectangle r, Point p) {
+ return p.longitude >= r.lo.longitude &&
+ p.longitude <= r.hi.longitude &&
+ p.latitude >= r.lo.latitude &&
+ p.latitude <= r.hi.latitude;
+ }
+
+ /// ListFeatures handler. Returns a stream of features within the given
+ /// rectangle.
+ @override
+ Stream<Feature> listFeatures(
+ grpc.ServiceCall call, Rectangle request) async* {
+ final normalizedRectangle = _normalize(request);
+ // For each feature, check if it is in the given bounding box
+ for (var feature in featuresDb) {
+ if (feature.name.isEmpty) continue;
+ final location = feature.location;
+ if (_contains(normalizedRectangle, location)) {
+ yield feature;
+ }
+ }
+ }
+
+ /// RecordRoute handler. Gets a stream of points, and responds with statistics
+ /// about the "trip": number of points, number of known features visited,
+ /// total distance traveled, and total time spent.
+ @override
+ Future<RouteSummary> recordRoute(
+ grpc.ServiceCall call, Stream<Point> request) async {
+ int pointCount = 0;
+ int featureCount = 0;
+ double distance = 0.0;
+ Point previous;
+ final timer = new Stopwatch();
+
+ await for (var location in request) {
+ if (!timer.isRunning) timer.start();
+ pointCount++;
+ final feature = featuresDb.firstWhere((f) => f.location == location,
+ orElse: () => null);
+ if (feature != null) {
+ featureCount++;
+ }
+ // For each point after the first, add the incremental distance from the
+ // previous point to the total distance value.
+ if (previous != null) distance += _distance(previous, location);
+ previous = location;
+ }
+ timer.stop();
+ return new RouteSummary()
+ ..pointCount = pointCount
+ ..featureCount = featureCount
+ ..distance = distance.round()
+ ..elapsedTime = timer.elapsed.inSeconds;
+ }
+
+ /// RouteChat handler. Receives a stream of message/location pairs, and
+ /// responds with a stream of all previous messages at each of those
+ /// locations.
+ @override
+ Stream<RouteNote> routeChat(
+ grpc.ServiceCall call, Stream<RouteNote> request) async* {
+ await for (var note in request) {
+ final notes = routeNotes.putIfAbsent(note.location, () => <RouteNote>[]);
+ for (var note in notes) yield note;
+ notes.add(note);
+ }
+ }
+
+ /// Calculate the distance between two points using the "haversine" formula.
+ /// This code was taken from http://www.movable-type.co.uk/scripts/latlong.html.
+ double _distance(Point start, Point end) {
+ double toRadians(double num) {
+ return num * pi / 180;
+ }
+
+ final lat1 = start.latitude / coordFactor;
+ final lat2 = end.latitude / coordFactor;
+ final lon1 = start.longitude / coordFactor;
+ final lon2 = end.longitude / coordFactor;
+ final R = 6371000; // metres
+ final phi1 = toRadians(lat1);
+ final phi2 = toRadians(lat2);
+ final dLat = toRadians(lat2 - lat1);
+ final dLon = toRadians(lon2 - lon1);
+
+ final a = sin(dLat / 2) * sin(dLat / 2) +
+ cos(phi1) * cos(phi2) * sin(dLon / 2) * sin(dLon / 2);
+ final c = 2 * atan2(sqrt(a), sqrt(1 - a));
+
+ return R * c;
+ }
+}
+
+class Server {
+ Future<void> main(List<String> args) async {
+ final server = new grpc.Server([new RouteGuideService()]);
+ await server.serve(port: 8080);
+ print('Server listening on port ${server.port}...');
+ }
+}
diff --git a/grpc/example/route_guide/protos/route_guide.proto b/grpc/example/route_guide/protos/route_guide.proto
new file mode 100644
index 0000000..12c4495
--- /dev/null
+++ b/grpc/example/route_guide/protos/route_guide.proto
@@ -0,0 +1,126 @@
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+option java_multiple_files = true;
+option java_package = "io.grpc.examples.routeguide";
+option java_outer_classname = "RouteGuideProto";
+option objc_class_prefix = "RTG";
+
+package routeguide;
+
+// Interface exported by the server.
+service RouteGuide {
+ // A simple RPC.
+ //
+ // Obtains the feature at a given position.
+ //
+ // A feature with an empty name is returned if there's no feature at the given
+ // position.
+ rpc GetFeature(Point) returns (Feature) {}
+
+ // A server-to-client streaming RPC.
+ //
+ // Obtains the Features available within the given Rectangle. Results are
+ // streamed rather than returned at once (e.g. in a response message with a
+ // repeated field), as the rectangle may cover a large area and contain a
+ // huge number of features.
+ rpc ListFeatures(Rectangle) returns (stream Feature) {}
+
+ // A client-to-server streaming RPC.
+ //
+ // Accepts a stream of Points on a route being traversed, returning a
+ // RouteSummary when traversal is completed.
+ rpc RecordRoute(stream Point) returns (RouteSummary) {}
+
+ // A Bidirectional streaming RPC.
+ //
+ // Accepts a stream of RouteNotes sent while a route is being traversed,
+ // while receiving other RouteNotes (e.g. from other users).
+ rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
+}
+
+// Points are represented as latitude-longitude pairs in the E7 representation
+// (degrees multiplied by 10**7 and rounded to the nearest integer).
+// Latitudes should be in the range +/- 90 degrees and longitude should be in
+// the range +/- 180 degrees (inclusive).
+message Point {
+ int32 latitude = 1;
+ int32 longitude = 2;
+}
+
+// A latitude-longitude rectangle, represented as two diagonally opposite
+// points "lo" and "hi".
+message Rectangle {
+ // One corner of the rectangle.
+ Point lo = 1;
+
+ // The other corner of the rectangle.
+ Point hi = 2;
+}
+
+// A feature names something at a given point.
+//
+// If a feature could not be named, the name is empty.
+message Feature {
+ // The name of the feature.
+ string name = 1;
+
+ // The point where the feature is detected.
+ Point location = 2;
+}
+
+// A RouteNote is a message sent while at a given point.
+message RouteNote {
+ // The location from which the message is sent.
+ Point location = 1;
+
+ // The message to be sent.
+ string message = 2;
+}
+
+// A RouteSummary is received in response to a RecordRoute rpc.
+//
+// It contains the number of individual points received, the number of
+// detected features, and the total distance covered as the cumulative sum of
+// the distance between each point.
+message RouteSummary {
+ // The number of points received.
+ int32 point_count = 1;
+
+ // The number of known features passed while traversing the route.
+ int32 feature_count = 2;
+
+ // The distance covered in metres.
+ int32 distance = 3;
+
+ // The duration of the traversal in seconds.
+ int32 elapsed_time = 4;
+}
diff --git a/grpc/example/route_guide/pubspec.yaml b/grpc/example/route_guide/pubspec.yaml
new file mode 100644
index 0000000..b247184
--- /dev/null
+++ b/grpc/example/route_guide/pubspec.yaml
@@ -0,0 +1,15 @@
+name: route_guide
+description: Dart gRPC sample client and server.
+homepage: https://github.com/dart-lang/grpc-dart
+
+environment:
+ sdk: '>=2.0.0 <3.0.0'
+
+dependencies:
+ async: '>=1.13.3 <3.0.0'
+ grpc:
+ path: ../../
+ protobuf: ^0.10.1
+
+dev_dependencies:
+ test: ^1.3.0
diff --git a/grpc/interop/bin/client.dart b/grpc/interop/bin/client.dart
new file mode 100644
index 0000000..be55d13
--- /dev/null
+++ b/grpc/interop/bin/client.dart
@@ -0,0 +1,110 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:async';
+
+import 'package:args/args.dart';
+
+import 'package:interop/src/client.dart';
+
+const _serverHostArgument = 'server_host';
+const _serverHostOverrideArgument = 'server_host_override';
+const _serverPortArgument = 'server_port';
+const _testCaseArgument = 'test_case';
+const _useTLSArgument = 'use_tls';
+const _useTestCAArgument = 'use_test_ca';
+const _defaultServiceAccountArgument = 'default_service_account';
+const _oauthScopeArgument = 'oauth_scope';
+const _serviceAccountKeyFileArgument = 'service_account_key_file';
+
+/// Clients implement test cases that test certain functionally. Each client is
+/// provided the test case it is expected to run as a command-line parameter.
+/// Names should be lowercase and without spaces.
+///
+/// Clients should accept these arguments:
+///
+/// * --server_host=HOSTNAME
+/// * The server host to connect to. For example, "localhost" or "127.0.0.1"
+/// * --server_host_override=HOSTNAME
+/// * The server host to claim to be connecting to, for use in TLS and
+/// HTTP/2 :authority header. If unspecified, the value of --server_host
+/// will be used
+/// * --server_port=PORT
+/// * The server port to connect to. For example, "8080"
+/// * --test_case=TESTCASE
+/// * The name of the test case to execute. For example, "empty_unary"
+/// * --use_tls=BOOLEAN
+/// * Whether to use a plaintext or encrypted connection
+/// * --use_test_ca=BOOLEAN
+/// * Whether to replace platform root CAs with ca.pem as the CA root
+/// * --default_service_account=ACCOUNT_EMAIL
+/// * Email of the GCE default service account.
+/// * --oauth_scope=SCOPE
+/// * OAuth scope. For example, "https://www.googleapis.com/auth/xapi.zoo"
+/// * --service_account_key_file=PATH
+/// * The path to the service account JSON key file generated from GCE
+/// developer console.
+///
+/// Clients must support TLS with ALPN. Clients must not disable certificate
+/// checking.
+Future<int> main(List<String> args) async {
+ final argumentParser = new ArgParser();
+ argumentParser.addOption(_serverHostArgument,
+ help: 'The server host to connect to. For example, "localhost" or '
+ '"127.0.0.1".');
+ argumentParser.addOption(_serverHostOverrideArgument,
+ help: 'The server host to claim to be connecting to, for use in TLS and '
+ 'HTTP/2 :authority header. If unspecified, the value of '
+ '--server_host will be used.');
+ argumentParser.addOption(_serverPortArgument,
+ help: 'The server port to connect to. For example, "8080".');
+ argumentParser.addOption(_testCaseArgument,
+ help:
+ 'The name of the test case to execute. For example, "empty_unary".');
+ argumentParser.addOption(_useTLSArgument,
+ defaultsTo: 'false',
+ help: 'Whether to use a plaintext or encrypted connection.');
+ argumentParser.addOption(_useTestCAArgument,
+ help: 'Whether to replace platform root CAs with ca.pem as the CA root.');
+ argumentParser.addOption(_defaultServiceAccountArgument,
+ help: 'Email of the GCE default service account.');
+ argumentParser.addOption(_oauthScopeArgument,
+ help: 'OAuth scope. For example, '
+ '"https://www.googleapis.com/auth/xapi.zoo".');
+ argumentParser.addOption(_serviceAccountKeyFileArgument,
+ help: 'The path to the service account JSON key file generated from GCE '
+ 'developer console.');
+ final arguments = argumentParser.parse(args);
+
+ final testClient = new Tester();
+
+ testClient.serverHost = arguments[_serverHostArgument];
+ testClient.serverHostOverride = arguments[_serverHostOverrideArgument];
+ testClient.serverPort = arguments[_serverPortArgument];
+ testClient.testCase = arguments[_testCaseArgument];
+ testClient.useTls = arguments[_useTLSArgument];
+ testClient.useTestCA = arguments[_useTestCAArgument];
+ testClient.defaultServiceAccount = arguments[_defaultServiceAccountArgument];
+ testClient.oauthScope = arguments[_oauthScopeArgument];
+ testClient.serviceAccountKeyFile = arguments[_serviceAccountKeyFileArgument];
+
+ if (!testClient.validate()) {
+ print(argumentParser.usage);
+ return -1;
+ }
+ await testClient.runTest();
+ print('Passed.');
+ return 0;
+}
diff --git a/grpc/interop/bin/server.dart b/grpc/interop/bin/server.dart
new file mode 100644
index 0000000..9cd7ca2
--- /dev/null
+++ b/grpc/interop/bin/server.dart
@@ -0,0 +1,141 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:args/args.dart';
+import 'package:grpc/grpc.dart';
+
+import 'package:interop/src/generated/empty.pb.dart';
+import 'package:interop/src/generated/messages.pb.dart';
+import 'package:interop/src/generated/test.pbgrpc.dart';
+
+const _headerEchoKey = 'x-grpc-test-echo-initial';
+const _trailerEchoKey = 'x-grpc-test-echo-trailing-bin';
+
+class TestService extends TestServiceBase {
+ @override
+ void $onMetadata(ServiceCall context) {
+ final headerEcho = context.clientMetadata[_headerEchoKey];
+ if (headerEcho != null) {
+ context.headers[_headerEchoKey] = headerEcho;
+ }
+ final trailerEcho = context.clientMetadata[_trailerEchoKey];
+ if (trailerEcho != null) {
+ context.trailers[_trailerEchoKey] = trailerEcho;
+ }
+ }
+
+ @override
+ Future<Empty> emptyCall(ServiceCall call, Empty request) async {
+ return new Empty();
+ }
+
+ @override
+ Future<SimpleResponse> unaryCall(
+ ServiceCall call, SimpleRequest request) async {
+ if (request.responseStatus.code != 0) {
+ throw new GrpcError.custom(
+ request.responseStatus.code, request.responseStatus.message);
+ }
+ final payload = new Payload()
+ ..body = new List.filled(request.responseSize, 0);
+ return new SimpleResponse()..payload = payload;
+ }
+
+ @override
+ Future<SimpleResponse> cacheableUnaryCall(
+ ServiceCall call, SimpleRequest request) async {
+ final timestamp = new DateTime.now().microsecond * 1000;
+ final responsePayload = new Payload()..body = ascii.encode('$timestamp');
+ return new SimpleResponse()..payload = responsePayload;
+ }
+
+ @override
+ Future<StreamingInputCallResponse> streamingInputCall(
+ ServiceCall call, Stream<StreamingInputCallRequest> request) async {
+ final aggregatedPayloadSize = await request.fold(
+ 0, (size, message) => size + message.payload.body.length);
+ return new StreamingInputCallResponse()
+ ..aggregatedPayloadSize = aggregatedPayloadSize;
+ }
+
+ Payload _payloadForRequest(ResponseParameters entry) =>
+ new Payload()..body = new List.filled(entry.size, 0);
+
+ @override
+ Stream<StreamingOutputCallResponse> streamingOutputCall(
+ ServiceCall call, StreamingOutputCallRequest request) async* {
+ for (final entry in request.responseParameters) {
+ if (entry.intervalUs > 0) {
+ await new Future.delayed(new Duration(microseconds: entry.intervalUs));
+ }
+ yield new StreamingOutputCallResponse()
+ ..payload = _payloadForRequest(entry);
+ }
+ }
+
+ StreamingOutputCallResponse _responseForRequest(
+ StreamingOutputCallRequest request) {
+ if (request.responseStatus.code != 0) {
+ throw new GrpcError.custom(
+ request.responseStatus.code, request.responseStatus.message);
+ }
+ final response = new StreamingOutputCallResponse();
+ if (request.responseParameters.isNotEmpty) {
+ response.payload = _payloadForRequest(request.responseParameters[0]);
+ }
+ return response;
+ }
+
+ @override
+ Stream<StreamingOutputCallResponse> fullDuplexCall(
+ ServiceCall call, Stream<StreamingOutputCallRequest> request) async* {
+ yield* request.map(_responseForRequest);
+ }
+
+ @override
+ Stream<StreamingOutputCallResponse> halfDuplexCall(
+ ServiceCall call, Stream<StreamingOutputCallRequest> request) async* {
+ final bufferedResponses = await request.map(_responseForRequest).toList();
+ yield* new Stream.fromIterable(bufferedResponses);
+ }
+}
+
+Future<void> main(List<String> args) async {
+ final argumentParser = new ArgParser();
+ argumentParser.addOption('port', defaultsTo: '8080');
+ argumentParser.addOption('use_tls', defaultsTo: 'false');
+ argumentParser.addOption('tls_cert_file', defaultsTo: 'server1.pem');
+ argumentParser.addOption('tls_key_file', defaultsTo: 'server1.key');
+ final arguments = argumentParser.parse(args);
+ final port = int.parse(arguments['port']);
+
+ final services = [new TestService()];
+
+ final server = new Server(services);
+
+ ServerTlsCredentials tlsCredentials;
+ if (arguments['use_tls'] == 'true') {
+ final certificate = new File(arguments['tls_cert_file']).readAsBytes();
+ final privateKey = new File(arguments['tls_key_file']).readAsBytes();
+ tlsCredentials = new ServerTlsCredentials(
+ certificate: await certificate, privateKey: await privateKey);
+ }
+ await server.serve(port: port, security: tlsCredentials);
+ print('Server listening on port ${server.port}...');
+}
diff --git a/grpc/interop/ca.pem b/grpc/interop/ca.pem
new file mode 100644
index 0000000..6c8511a
--- /dev/null
+++ b/grpc/interop/ca.pem
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE-----
+MIICSjCCAbOgAwIBAgIJAJHGGR4dGioHMA0GCSqGSIb3DQEBCwUAMFYxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQxDzANBgNVBAMTBnRlc3RjYTAeFw0xNDExMTEyMjMxMjla
+Fw0yNDExMDgyMjMxMjlaMFYxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0
+YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxDzANBgNVBAMT
+BnRlc3RjYTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwEDfBV5MYdlHVHJ7
++L4nxrZy7mBfAVXpOc5vMYztssUI7mL2/iYujiIXM+weZYNTEpLdjyJdu7R5gGUu
+g1jSVK/EPHfc74O7AyZU34PNIP4Sh33N+/A5YexrNgJlPY+E3GdVYi4ldWJjgkAd
+Qah2PH5ACLrIIC6tRka9hcaBlIECAwEAAaMgMB4wDAYDVR0TBAUwAwEB/zAOBgNV
+HQ8BAf8EBAMCAgQwDQYJKoZIhvcNAQELBQADgYEAHzC7jdYlzAVmddi/gdAeKPau
+sPBG/C2HCWqHzpCUHcKuvMzDVkY/MP2o6JIW2DBbY64bO/FceExhjcykgaYtCH/m
+oIU63+CFOTtR7otyQAWHqXa7q4SbCDlG7DyRFxqG0txPtGvy12lgldA2+RgcigQG
+Dfcog5wrJytaQ6UA0wE=
+-----END CERTIFICATE-----
diff --git a/grpc/interop/lib/src/client.dart b/grpc/interop/lib/src/client.dart
new file mode 100644
index 0000000..d47895f
--- /dev/null
+++ b/grpc/interop/lib/src/client.dart
@@ -0,0 +1,1189 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:async';
+import 'dart:io';
+import 'dart:typed_data';
+
+import 'package:collection/collection.dart';
+import 'package:grpc/grpc.dart';
+import 'package:interop/src/generated/empty.pb.dart';
+import 'package:interop/src/generated/messages.pb.dart';
+import 'package:interop/src/generated/test.pbgrpc.dart';
+
+const _headerEchoKey = 'x-grpc-test-echo-initial';
+const _headerEchoData = 'test_initial_metadata_value';
+
+const _trailerEchoKey = 'x-grpc-test-echo-trailing-bin';
+const _trailerEchoData = 'q6ur'; // 0xababab in base64
+
+class Tester {
+ String serverHost;
+ String serverHostOverride;
+ int _serverPort;
+ String testCase;
+ bool _useTls;
+ bool _useTestCA;
+ String defaultServiceAccount;
+ String oauthScope;
+ String serviceAccountKeyFile;
+ String _serviceAccountJson;
+
+ String get serviceAccountJson =>
+ _serviceAccountJson ??= _readServiceAccountJson();
+
+ String _readServiceAccountJson() {
+ if (serviceAccountKeyFile?.isEmpty ?? true) {
+ throw 'Service account key file not specified.';
+ }
+ return new File(serviceAccountKeyFile).readAsStringSync();
+ }
+
+ void set serverPort(String value) {
+ if (value == null) {
+ _serverPort = null;
+ return;
+ }
+ try {
+ _serverPort = int.parse(value);
+ } catch (e) {
+ print('Invalid port "$value": $e');
+ }
+ }
+
+ void set useTls(String value) {
+ _useTls = value != 'false';
+ }
+
+ void set useTestCA(String value) {
+ _useTestCA = value == 'true';
+ }
+
+ ClientChannel channel;
+ TestServiceClient client;
+ UnimplementedServiceClient unimplementedServiceClient;
+
+ bool validate() {
+ if (serverHost == null) {
+ print('Must specify --server_host');
+ return false;
+ }
+ if (_serverPort == null) {
+ print('Must specify --server_port');
+ return false;
+ }
+
+ return true;
+ }
+
+ Future<void> runTest() async {
+ ChannelCredentials credentials;
+ if (_useTls) {
+ List<int> trustedRoot;
+ if (_useTestCA) {
+ trustedRoot = new File('ca.pem').readAsBytesSync();
+ }
+ credentials = new ChannelCredentials.secure(
+ certificates: trustedRoot, authority: serverHostOverride);
+ } else {
+ credentials = const ChannelCredentials.insecure();
+ }
+
+ final options = new ChannelOptions(credentials: credentials);
+ channel =
+ new ClientChannel(serverHost, port: _serverPort, options: options);
+ client = new TestServiceClient(channel);
+ unimplementedServiceClient = new UnimplementedServiceClient(channel);
+ await runTestCase();
+ await channel.shutdown();
+ }
+
+ Future<void> runTestCase() async {
+ switch (testCase) {
+ case 'empty_unary':
+ return emptyUnary();
+ case 'cacheable_unary':
+ return cacheableUnary();
+ case 'large_unary':
+ return largeUnary();
+ case 'client_compressed_unary':
+ return clientCompressedUnary();
+ case 'server_compressed_unary':
+ return serverCompressedUnary();
+ case 'client_streaming':
+ return clientStreaming();
+ case 'client_compressed_streaming':
+ return clientCompressedStreaming();
+ case 'server_streaming':
+ return serverStreaming();
+ case 'server_compressed_streaming':
+ return serverCompressedStreaming();
+ case 'ping_pong':
+ return pingPong();
+ case 'empty_stream':
+ return emptyStream();
+ case 'compute_engine_creds':
+ return computeEngineCreds();
+ case 'service_account_creds':
+ return serviceAccountCreds();
+ case 'jwt_token_creds':
+ return jwtTokenCreds();
+ case 'oauth2_auth_token':
+ return oauth2AuthToken();
+ case 'per_rpc_creds':
+ return perRpcCreds();
+ case 'custom_metadata':
+ return customMetadata();
+ case 'status_code_and_message':
+ return statusCodeAndMessage();
+ case 'unimplemented_method':
+ return unimplementedMethod();
+ case 'unimplemented_service':
+ return unimplementedService();
+ case 'cancel_after_begin':
+ return cancelAfterBegin();
+ case 'cancel_after_first_response':
+ return cancelAfterFirstResponse();
+ case 'timeout_on_sleeping_server':
+ return timeoutOnSleepingServer();
+ default:
+ print('Unknown test case: $testCase');
+ }
+ }
+
+ /// This test verifies that implementations support zero-size messages.
+ /// Ideally, client implementations would verify that the request and response
+ /// were zero bytes serialized, but this is generally prohibitive to perform,
+ /// so is not required.
+ ///
+ /// Procedure:
+ /// 1. Client calls EmptyCall with the default Empty message
+ ///
+ /// Client asserts:
+ /// * call was successful
+ /// * response is non-null
+ Future<void> emptyUnary() async {
+ final response = await client.emptyCall(new Empty());
+ if (response == null) throw 'Expected non-null response.';
+ if (response is! Empty) throw 'Expected Empty response.';
+ }
+
+ /// This test verifies that gRPC requests marked as cacheable use GET verb
+ /// instead of POST, and that server sets appropriate cache control headers
+ /// for the response to be cached by a proxy. This test requires that the
+ /// server is behind a caching proxy. Use of current timestamp in the request
+ /// prevents accidental cache matches left over from previous tests.
+ ///
+ /// Procedure:
+ /// 1. Client calls CacheableUnaryCall with `SimpleRequest` request with
+ /// payload set to current timestamp. Timestamp format is irrelevant, and
+ /// resolution is in nanoseconds.
+ /// Client adds a `x-user-ip` header with value `1.2.3.4` to the request.
+ /// This is done since some proxys such as GFE will not cache requests from
+ /// localhost.
+ /// Client marks the request as cacheable by setting the cacheable flag in
+ /// the request context. Longer term this should be driven by the method
+ /// option specified in the proto file itself.
+ /// 2. Client calls CacheableUnaryCall again immediately with the same request
+ /// and configuration as the previous call.
+ ///
+ /// Client asserts:
+ /// * Both calls were successful
+ /// * The payload body of both responses is the same.
+ Future<void> cacheableUnary() async {
+ throw 'Not implemented';
+ }
+
+ /// This test verifies unary calls succeed in sending messages, and touches on
+ /// flow control (even if compression is enabled on the channel).
+ ///
+ /// Procedure:
+ /// 1. Client calls UnaryCall with:
+ /// {
+ /// response_size: 314159
+ /// payload: {
+ /// body: 271828 bytes of zeros
+ /// }
+ /// }
+ ///
+ /// Client asserts:
+ /// * call was successful
+ /// * response payload body is 314159 bytes in size
+ /// * clients are free to assert that the response payload body contents are
+ /// zero and comparing the entire response message against a golden response
+ Future<void> largeUnary() async {
+ final payload = new Payload()..body = new Uint8List(271828);
+ final request = new SimpleRequest()
+ ..responseSize = 314159
+ ..payload = payload;
+ final response = await client.unaryCall(request);
+ final receivedBytes = response.payload.body.length;
+ if (receivedBytes != 314159) {
+ throw 'Response payload mismatch. Expected 314159 bytes, '
+ 'got ${receivedBytes}.';
+ }
+ }
+
+ /// This test verifies the client can compress unary messages by sending two
+ /// unary calls, for compressed and uncompressed payloads. It also sends an
+ /// initial probing request to verify whether the server supports the
+ /// CompressedRequest feature by checking if the probing call fails with an
+ /// `INVALID_ARGUMENT` status.
+ ///
+ /// Procedure:
+ /// 1. Client calls UnaryCall with the feature probe, an *uncompressed*
+ /// message:
+ /// {
+ /// expect_compressed: {
+ /// value: true
+ /// }
+ /// response_size: 314159
+ /// payload: {
+ /// body: 271828 bytes of zeros
+ /// }
+ /// }
+ /// 1. Client calls UnaryCall with the *compressed* message:
+ /// {
+ /// expect_compressed: {
+ /// value: true
+ /// }
+ /// response_size: 314159
+ /// payload: {
+ /// body: 271828 bytes of zeros
+ /// }
+ /// }
+ /// 1. Client calls UnaryCall with the *uncompressed* message:
+ /// {
+ /// expect_compressed: {
+ /// value: false
+ /// }
+ /// response_size: 314159
+ /// payload: {
+ /// body: 271828 bytes of zeros
+ /// }
+ /// }
+ ///
+ /// Client asserts:
+ /// * First call failed with `INVALID_ARGUMENT` status.
+ /// * Subsequent calls were successful.
+ /// * Response payload body is 314159 bytes in size.
+ /// * Clients are free to assert that the response payload body contents are
+ /// zeros and comparing the entire response message against a golden
+ /// response.
+ Future<void> clientCompressedUnary() async {
+ throw 'Not implemented';
+ }
+
+ /// This test verifies the server can compress unary messages. It sends two
+ /// unary requests, expecting the server's response to be compressed or not
+ /// according to the `response_compressed` boolean.
+ ///
+ /// Whether compression was actually performed is determined by the
+ /// compression bit in the response's message flags. *Note that some languages
+ /// may not have access to the message flags, in which case the client will be
+ /// unable to verify that the `response_compressed` boolean is obeyed by the
+ /// server*.
+ ///
+ /// Procedure:
+ /// 1. Client calls UnaryCall with `SimpleRequest`:
+ /// {
+ /// response_compressed: {
+ /// value: true
+ /// }
+ /// response_size: 314159
+ /// payload: {
+ /// body: 271828 bytes of zeros
+ /// }
+ /// }
+ /// {
+ /// response_compressed: {
+ /// value: false
+ /// }
+ /// response_size: 314159
+ /// payload: {
+ /// body: 271828 bytes of zeros
+ /// }
+ /// }
+ ///
+ /// Client asserts:
+ /// * call was successful
+ /// * if supported by the implementation, when `response_compressed` is true,
+ /// the response MUST have the compressed message flag set.
+ /// * if supported by the implementation, when `response_compressed` is false,
+ /// the response MUST NOT have the compressed message flag set.
+ /// * response payload body is 314159 bytes in size in both cases.
+ /// * clients are free to assert that the response payload body contents are
+ /// zero and comparing the entire response message against a golden response
+ Future<void> serverCompressedUnary() async {
+ throw 'Not implemented';
+ }
+
+ /// This test verifies that client-only streaming succeeds.
+ ///
+ /// Procedure:
+ /// 1. Client calls StreamingInputCall
+ /// 2. Client sends:
+ /// {
+ /// payload: {
+ /// body: 27182 bytes of zeros
+ /// }
+ /// }
+ /// 3. Client then sends:
+ /// {
+ /// payload: {
+ /// body: 8 bytes of zeros
+ /// }
+ /// }
+ /// 4. Client then sends:
+ /// {
+ /// payload: {
+ /// body: 1828 bytes of zeros
+ /// }
+ /// }
+ /// 5. Client then sends:
+ /// {
+ /// payload: {
+ /// body: 45904 bytes of zeros
+ /// }
+ /// }
+ /// 6. Client half-closes
+ ///
+ /// Client asserts:
+ /// * call was successful
+ /// * response aggregated_payload_size is 74922
+ Future<void> clientStreaming() async {
+ StreamingInputCallRequest createRequest(int bytes) {
+ final request = new StreamingInputCallRequest()..payload = new Payload();
+ request.payload.body = new Uint8List(bytes);
+ return request;
+ }
+
+ Stream<StreamingInputCallRequest> requests() async* {
+ yield createRequest(27182);
+ yield createRequest(8);
+ yield createRequest(1828);
+ yield createRequest(45904);
+ }
+
+ final response = await client.streamingInputCall(requests());
+ if (response.aggregatedPayloadSize != 74922) {
+ throw 'Response mismatch. Expected 74922, '
+ 'got ${response.aggregatedPayloadSize}';
+ }
+ }
+
+ /// This test verifies the client can compress requests on per-message basis
+ /// by performing a two-request streaming call. It also sends an initial
+ /// probing request to verify whether the server supports the
+ /// CompressedRequest feature by checking if the probing call fails with an
+ /// `INVALID_ARGUMENT` status.
+ ///
+ /// Procedure:
+ /// 1. Client calls `StreamingInputCall` and sends the following
+ /// feature-probing *uncompressed* `StreamingInputCallRequest` message
+ /// {
+ /// expect_compressed: {
+ /// value: true
+ /// }
+ /// payload: {
+ /// body: 27182 bytes of zeros
+ /// }
+ /// }
+ /// If the call does not fail with `INVALID_ARGUMENT`, the test fails.
+ /// Otherwise, we continue.
+ /// 1. Client calls `StreamingInputCall` again, sending the *compressed*
+ /// message
+ /// {
+ /// expect_compressed: {
+ /// value: true
+ /// }
+ /// payload: {
+ /// body: 27182 bytes of zeros
+ /// }
+ /// }
+ /// 1. And finally, the *uncompressed* message
+ /// {
+ /// expect_compressed: {
+ /// value: false
+ /// }
+ /// payload: {
+ /// body: 45904 bytes of zeros
+ /// }
+ /// }
+ /// 1. Client half-closes
+ ///
+ /// Client asserts:
+ /// * First call fails with `INVALID_ARGUMENT`.
+ /// * Next calls succeeds.
+ /// * Response aggregated payload size is 73086.
+ Future<void> clientCompressedStreaming() async {
+ throw 'Not implemented';
+ }
+
+ /// This test verifies that server-only streaming succeeds.
+ ///
+ /// Procedure:
+ /// 1. Client calls StreamingOutputCall with `StreamingOutputCallRequest`:
+ /// {
+ /// response_parameters: {
+ /// size: 31415
+ /// }
+ /// response_parameters: {
+ /// size: 9
+ /// }
+ /// response_parameters: {
+ /// size: 2653
+ /// }
+ /// response_parameters: {
+ /// size: 58979
+ /// }
+ /// }
+ ///
+ /// Client asserts:
+ /// * call was successful
+ /// * exactly four responses
+ /// * response payload bodies are sized (in order): 31415, 9, 2653, 58979
+ /// * clients are free to assert that the response payload body contents are
+ /// zero and comparing the entire response messages against golden responses
+ Future<void> serverStreaming() async {
+ final expectedResponses = [31415, 9, 2653, 58979];
+
+ final request = new StreamingOutputCallRequest()
+ ..responseParameters.addAll(expectedResponses
+ .map((size) => new ResponseParameters()..size = size));
+
+ final responses = await client.streamingOutputCall(request).toList();
+ if (responses.length != 4) {
+ throw 'Incorrect number of responses (${responses.length}).';
+ }
+ final responseLengths =
+ responses.map((response) => response.payload.body.length).toList();
+
+ if (!new ListEquality().equals(responseLengths, expectedResponses)) {
+ throw 'Incorrect response lengths received (${responseLengths.join(
+ ', ')} != ${expectedResponses.join(', ')})';
+ }
+ }
+
+ /// This test verifies that the server can compress streaming messages and
+ /// disable compression on individual messages, expecting the server's
+ /// response to be compressed or not according to the `response_compressed`
+ /// boolean.
+ ///
+ /// Whether compression was actually performed is determined by the
+ /// compression bit in the response's message flags. *Note that some languages
+ /// may not have access to the message flags, in which case the client will be
+ /// unable to verify that the `response_compressed` boolean is obeyed by the
+ /// server*.
+ ///
+ /// Procedure:
+ /// 1. Client calls StreamingOutputCall with `StreamingOutputCallRequest`:
+ /// {
+ /// response_parameters: {
+ /// compressed: {
+ /// value: true
+ /// }
+ /// size: 31415
+ /// }
+ /// response_parameters: {
+ /// compressed: {
+ /// value: false
+ /// }
+ /// size: 92653
+ /// }
+ /// }
+ ///
+ /// Client asserts:
+ /// * call was successful
+ /// * exactly two responses
+ /// * if supported by the implementation, when `response_compressed` is false,
+ /// the response's messages MUST NOT have the compressed message flag set.
+ /// * if supported by the implementation, when `response_compressed` is true,
+ /// the response's messages MUST have the compressed message flag set.
+ /// * response payload bodies are sized (in order): 31415, 92653
+ /// * clients are free to assert that the response payload body contents are
+ /// zero and comparing the entire response messages against golden responses
+ Future<void> serverCompressedStreaming() async {
+ throw 'Not implemented';
+ }
+
+ /// This test verifies that full duplex bidi is supported.
+ ///
+ /// Procedure:
+ /// 1. Client calls FullDuplexCall with:
+ /// {
+ /// response_parameters: {
+ /// size: 31415
+ /// }
+ /// payload: {
+ /// body: 27182 bytes of zeros
+ /// }
+ /// }
+ /// 2. After getting a reply, it sends:
+ /// {
+ /// response_parameters: {
+ /// size: 9
+ /// }
+ /// payload: {
+ /// body: 8 bytes of zeros
+ /// }
+ /// }
+ /// 3. After getting a reply, it sends:
+ /// {
+ /// response_parameters: {
+ /// size: 2653
+ /// }
+ /// payload: {
+ /// body: 1828 bytes of zeros
+ /// }
+ /// }
+ /// 4. After getting a reply, it sends:
+ /// {
+ /// response_parameters: {
+ /// size: 58979
+ /// }
+ /// payload: {
+ /// body: 45904 bytes of zeros
+ /// }
+ /// }
+ /// 5. After getting a reply, client half-closes
+ ///
+ /// Client asserts:
+ /// * call was successful
+ /// * exactly four responses
+ /// * response payload bodies are sized (in order): 31415, 9, 2653, 58979
+ /// * clients are free to assert that the response payload body contents are
+ /// zero and comparing the entire response messages against golden responses
+ Future<void> pingPong() async {
+ final requestSizes = [27182, 8, 1828, 45904];
+ final expectedResponses = [31415, 9, 2653, 58979];
+
+ StreamingOutputCallRequest createRequest(int index) {
+ final payload = new Payload()..body = new Uint8List(requestSizes[index]);
+ final request = new StreamingOutputCallRequest()
+ ..payload = payload
+ ..responseParameters
+ .add(new ResponseParameters()..size = expectedResponses[index]);
+ return request;
+ }
+
+ var index = 0;
+ final requests = new StreamController<int>();
+
+ final responses = client.fullDuplexCall(requests.stream.map(createRequest));
+ requests.add(index);
+ await for (final response in responses) {
+ if (index >= expectedResponses.length) {
+ throw 'Received too many responses. $index > ${expectedResponses
+ .length}.';
+ }
+ if (response.payload.body.length != expectedResponses[index]) {
+ throw 'Response mismatch for response $index: '
+ '${response.payload.body.length} != ${expectedResponses[index]}.';
+ }
+ index++;
+ if (index == requestSizes.length) {
+ requests.close();
+ } else {
+ requests.add(index);
+ }
+ }
+ }
+
+ /// This test verifies that streams support having zero-messages in both
+ /// directions.
+ ///
+ /// Procedure:
+ /// 1. Client calls FullDuplexCall and then half-closes
+ ///
+ /// Client asserts:
+ /// * call was successful
+ /// * exactly zero responses
+ Future<void> emptyStream() async {
+ final requests = new StreamController<StreamingOutputCallRequest>();
+ final call = client.fullDuplexCall(requests.stream);
+ requests.close();
+ final responses = await call.toList();
+ if (responses.length != 0) {
+ throw 'Received too many responses. ${responses.length} != 0';
+ }
+ }
+
+ /// This test is only for cloud-to-prod path.
+ ///
+ /// This test verifies unary calls succeed in sending messages while using
+ /// Service Credentials from GCE metadata server. The client instance needs to
+ /// be created with desired oauth scope.
+ ///
+ /// The test uses `--default_service_account` with GCE service account email
+ /// and `--oauth_scope` with the OAuth scope to use. For testing against
+ /// grpc-test.sandbox.googleapis.com,
+ /// "https://www.googleapis.com/auth/xapi.zoo" should be passed in as
+ /// `--oauth_scope`.
+ ///
+ /// Procedure:
+ /// 1. Client configures channel to use GCECredentials
+ /// 2. Client calls UnaryCall on the channel with:
+ /// {
+ /// response_size: 314159
+ /// payload: {
+ /// body: 271828 bytes of zeros
+ /// }
+ /// fill_username: true
+ /// fill_oauth_scope: true
+ /// }
+ ///
+ /// Client asserts:
+ /// * call was successful
+ /// * received SimpleResponse.username equals the value of
+ /// `--default_service_account` flag
+ /// * received SimpleResponse.oauth_scope is in `--oauth_scope`
+ /// * response payload body is 314159 bytes in size
+ /// * clients are free to assert that the response payload body contents are
+ /// zero and comparing the entire response message against a golden response
+ Future<void> computeEngineCreds() async {
+ final credentials = new ComputeEngineAuthenticator();
+ final clientWithCredentials =
+ new TestServiceClient(channel, options: credentials.toCallOptions);
+
+ final response = await _sendSimpleRequestForAuth(clientWithCredentials,
+ fillUsername: true, fillOauthScope: true);
+
+ final user = response.username;
+ final oauth = response.oauthScope;
+
+ if (user?.isEmpty ?? true) {
+ throw 'Username not received.';
+ }
+ if (oauth?.isEmpty ?? true) {
+ throw 'OAuth scope not received.';
+ }
+
+ if (user != defaultServiceAccount) {
+ throw 'Got user name $user, wanted $defaultServiceAccount';
+ }
+ if (!oauthScope.contains(oauth)) {
+ throw 'Got OAuth scope $oauth, which is not a substring of $oauthScope';
+ }
+ }
+
+ /// This test is only for cloud-to-prod path.
+ ///
+ /// This test verifies unary calls succeed in sending messages while using
+ /// service account credentials.
+ ///
+ /// Test caller should set flag `--service_account_key_file` with the path to
+ /// json key file downloaded from https://console.developers.google.com.
+ /// Alternately, if using a usable auth implementation, she may specify the
+ /// file location in the environment variable GOOGLE_APPLICATION_CREDENTIALS.
+ ///
+ /// Procedure:
+ /// 1. Client configures the channel to use ServiceAccountCredentials
+ /// 2. Client calls UnaryCall with:
+ /// {
+ /// response_size: 314159
+ /// payload: {
+ /// body: 271828 bytes of zeros
+ /// }
+ /// fill_username: true
+ /// }
+ ///
+ /// Client asserts:
+ /// * call was successful
+ /// * received SimpleResponse.username is not empty and is in the json key
+ /// file used by the auth library. The client can optionally check the
+ /// username matches the email address in the key file or equals the value
+ /// of `--default_service_account` flag.
+ /// * response payload body is 314159 bytes in size
+ /// * clients are free to assert that the response payload body contents are
+ /// zero and comparing the entire response message against a golden response
+ Future<void> serviceAccountCreds() async {
+ throw 'Not implemented';
+ }
+
+ /// This test is only for cloud-to-prod path.
+ ///
+ /// This test verifies unary calls succeed in sending messages while using JWT
+ /// token (created by the project's key file)
+ ///
+ /// Test caller should set flag `--service_account_key_file` with the path to
+ /// json key file downloaded from https://console.developers.google.com.
+ /// Alternately, if using a usable auth implementation, she may specify the
+ /// file location in the environment variable GOOGLE_APPLICATION_CREDENTIALS.
+ ///
+ /// Procedure:
+ /// 1. Client configures the channel to use JWTTokenCredentials
+ /// 2. Client calls UnaryCall with:
+ /// {
+ /// response_size: 314159
+ /// payload: {
+ /// body: 271828 bytes of zeros
+ /// }
+ /// fill_username: true
+ /// }
+ ///
+ /// Client asserts:
+ /// * call was successful
+ /// * received SimpleResponse.username is not empty and is in the json key
+ /// file used by the auth library. The client can optionally check the
+ /// username matches the email address in the key file or equals the value
+ /// of `--default_service_account` flag.
+ /// * response payload body is 314159 bytes in size
+ /// * clients are free to assert that the response payload body contents are
+ /// zero and comparing the entire response message against a golden response
+ Future<void> jwtTokenCreds() async {
+ final credentials = new JwtServiceAccountAuthenticator(serviceAccountJson);
+ final clientWithCredentials =
+ new TestServiceClient(channel, options: credentials.toCallOptions);
+
+ final response = await _sendSimpleRequestForAuth(clientWithCredentials,
+ fillUsername: true);
+ final username = response.username;
+ if (username?.isEmpty ?? true) {
+ throw 'Username not received.';
+ }
+ if (!serviceAccountJson.contains(username)) {
+ throw 'Got user name $username, which is not a substring of $serviceAccountJson';
+ }
+ }
+
+ /// This test is only for cloud-to-prod path and some implementations may run
+ /// in GCE only.
+ ///
+ /// This test verifies unary calls succeed in sending messages using an OAuth2
+ /// token that is obtained out of band. For the purpose of the test, the
+ /// OAuth2 token is actually obtained from a service account credentials or
+ /// GCE credentials via the language-specific authorization library.
+ ///
+ /// The difference between this test and the other auth tests is that it first
+ /// uses the authorization library to obtain an authorization token.
+ ///
+ /// The test
+ /// * uses the flag `--service_account_key_file` with the path to a json key
+ /// file downloaded from https://console.developers.google.com. Alternately,
+ /// if using a usable auth implementation, it may specify the file location
+ /// in the environment variable GOOGLE_APPLICATION_CREDENTIALS, *OR* if GCE
+ /// credentials is used to fetch the token, `--default_service_account` can
+ /// be used to pass in GCE service account email.
+ /// * uses the flag `--oauth_scope` for the oauth scope. For testing against
+ /// grpc-test.sandbox.googleapis.com,
+ /// "https://www.googleapis.com/auth/xapi.zoo" should be passed as the
+ /// `--oauth_scope`.
+ ///
+ /// Procedure:
+ /// 1. Client uses the auth library to obtain an authorization token
+ /// 2. Client configures the channel to use AccessTokenCredentials with the
+ /// access token obtained in step 1
+ /// 3. Client calls UnaryCall with the following message
+ /// {
+ /// fill_username: true
+ /// fill_oauth_scope: true
+ /// }
+ ///
+ /// Client asserts:
+ /// * call was successful
+ /// * received SimpleResponse.username is valid. Depending on whether a
+ /// service account key file or GCE credentials was used, client should
+ /// check against the json key file or GCE default service account email.
+ /// * received SimpleResponse.oauth_scope is in `--oauth_scope`
+ Future<void> oauth2AuthToken() async {
+ final credentials =
+ new ServiceAccountAuthenticator(serviceAccountJson, [oauthScope]);
+ final clientWithCredentials =
+ new TestServiceClient(channel, options: credentials.toCallOptions);
+
+ final response = await _sendSimpleRequestForAuth(clientWithCredentials,
+ fillUsername: true, fillOauthScope: true);
+
+ final user = response.username;
+ final oauth = response.oauthScope;
+
+ if (user?.isEmpty ?? true) {
+ throw 'Username not received.';
+ }
+ if (oauth?.isEmpty ?? true) {
+ throw 'OAuth scope not received.';
+ }
+
+ if (!serviceAccountJson.contains(user)) {
+ throw 'Got user name $user, which is not a substring of $serviceAccountJson';
+ }
+ if (!oauthScope.contains(oauth)) {
+ throw 'Got OAuth scope $oauth, which is not a substring of $oauthScope';
+ }
+ }
+
+ /// Similar to the other auth tests, this test is only for cloud-to-prod path.
+ ///
+ /// This test verifies unary calls succeed in sending messages using a JWT or
+ /// a service account credentials set on the RPC.
+ ///
+ /// The test
+ /// * uses the flag `--service_account_key_file` with the path to a json key
+ /// file downloaded from https://console.developers.google.com. Alternately,
+ /// if using a usable auth implementation, it may specify the file location
+ /// in the environment variable GOOGLE_APPLICATION_CREDENTIALS
+ /// * optionally uses the flag `--oauth_scope` for the oauth scope if
+ /// implementator wishes to use service account credential instead of JWT
+ /// credential. For testing against grpc-test.sandbox.googleapis.com, oauth
+ /// scope "https://www.googleapis.com/auth/xapi.zoo" should be used.
+ ///
+ /// Procedure:
+ /// 1. Client configures the channel with just SSL credentials
+ /// 2. Client calls UnaryCall, setting per-call credentials to
+ /// JWTTokenCredentials. The request is the following message
+ /// {
+ /// fill_username: true
+ /// }
+ ///
+ /// Client asserts:
+ /// * call was successful
+ /// * received SimpleResponse.username is not empty and is in the json key
+ /// file used by the auth library. The client can optionally check the
+ /// username matches the email address in the key file.
+ Future<void> perRpcCreds() async {
+ final credentials =
+ new ServiceAccountAuthenticator(serviceAccountJson, [oauthScope]);
+
+ final response = await _sendSimpleRequestForAuth(client,
+ fillUsername: true,
+ fillOauthScope: true,
+ options: credentials.toCallOptions);
+
+ final user = response.username;
+ final oauth = response.oauthScope;
+
+ if (user?.isEmpty ?? true) {
+ throw 'Username not received.';
+ }
+ if (oauth?.isEmpty ?? true) {
+ throw 'OAuth scope not received.';
+ }
+
+ if (!serviceAccountJson.contains(user)) {
+ throw 'Got user name $user, which is not a substring of $serviceAccountJson';
+ }
+ if (!oauthScope.contains(oauth)) {
+ throw 'Got OAuth scope $oauth, which is not a substring of $oauthScope';
+ }
+ }
+
+ Future<SimpleResponse> _sendSimpleRequestForAuth(TestServiceClient client,
+ {bool fillUsername: false,
+ bool fillOauthScope: false,
+ CallOptions options}) async {
+ final payload = new Payload()..body = new Uint8List(271828);
+ final request = new SimpleRequest()
+ ..responseSize = 314159
+ ..payload = payload
+ ..fillUsername = fillUsername
+ ..fillOauthScope = fillOauthScope;
+ final response = await client.unaryCall(request, options: options);
+ final receivedBytes = response.payload.body.length;
+ if (receivedBytes != 314159) {
+ throw 'Response payload mismatch. Expected 314159 bytes, '
+ 'got ${receivedBytes}.';
+ }
+ return response;
+ }
+
+ /// This test verifies that custom metadata in either binary or ascii format
+ /// can be sent as initial-metadata by the client and as both initial- and
+ /// trailing-metadata by the server.
+ ///
+ /// Procedure:
+ /// 1. The client attaches custom metadata with the following keys and values:
+ /// key: "x-grpc-test-echo-initial", value: "test_initial_metadata_value"
+ /// key: "x-grpc-test-echo-trailing-bin", value: 0xababab
+ /// to a UnaryCall with request:
+ /// {
+ /// response_size: 314159
+ /// payload: {
+ /// body: 271828 bytes of zeros
+ /// }
+ /// }
+ ///
+ /// 2. The client attaches custom metadata with the following keys and values:
+ /// key: "x-grpc-test-echo-initial", value: "test_initial_metadata_value"
+ /// key: "x-grpc-test-echo-trailing-bin", value: 0xababab
+ /// to a FullDuplexCall with request:
+ /// {
+ /// response_parameters: {
+ /// size: 314159
+ /// }
+ /// payload: {
+ /// body: 271828 bytes of zeros
+ /// }
+ /// }
+ /// and then half-closes
+ ///
+ /// Client asserts:
+ /// * call was successful
+ /// * metadata with key `"x-grpc-test-echo-initial"` and value
+ /// `"test_initial_metadata_value"`is received in the initial metadata for
+ /// calls in Procedure steps 1 and 2.
+ /// * metadata with key `"x-grpc-test-echo-trailing-bin"` and value `0xababab`
+ /// is received in the trailing metadata for calls in Procedure steps 1 and
+ /// 2.
+ Future<void> customMetadata() async {
+ void validate(Map<String, String> headers, Map<String, String> trailers) {
+ if (headers[_headerEchoKey] != _headerEchoData) {
+ throw 'Invalid header data received.';
+ }
+ if (trailers[_trailerEchoKey] != _trailerEchoData) {
+ throw 'Invalid trailer data received.';
+ }
+ }
+
+ final options = new CallOptions(metadata: {
+ _headerEchoKey: _headerEchoData,
+ _trailerEchoKey: _trailerEchoData,
+ });
+ final unaryCall = client.unaryCall(
+ new SimpleRequest()
+ ..responseSize = 314159
+ ..payload = (new Payload()..body = new Uint8List(271828)),
+ options: options);
+ var headers = await unaryCall.headers;
+ var trailers = await unaryCall.trailers;
+ await unaryCall;
+ validate(headers, trailers);
+
+ Stream<StreamingOutputCallRequest> requests() async* {
+ yield new StreamingOutputCallRequest()
+ ..responseParameters.add(new ResponseParameters()..size = 314159)
+ ..payload = (new Payload()..body = new Uint8List(271828));
+ }
+
+ final fullDuplexCall = client.fullDuplexCall(requests(), options: options);
+ final drain = fullDuplexCall.drain();
+ headers = await fullDuplexCall.headers;
+ trailers = await fullDuplexCall.trailers;
+ await drain;
+ validate(headers, trailers);
+ }
+
+ /// This test verifies unary calls succeed in sending messages, and propagate
+ /// back status code and message sent along with the messages.
+ ///
+ /// Procedure:
+ /// 1. Client calls UnaryCall with:
+ /// {
+ /// response_status: {
+ /// code: 2
+ /// message: "test status message"
+ /// }
+ /// }
+ ///
+ /// 2. Client calls FullDuplexCall with:
+ /// {
+ /// response_status: {
+ /// code: 2
+ /// message: "test status message"
+ /// }
+ /// }
+ ///
+ /// and then half-closes
+ ///
+ /// Client asserts:
+ /// * received status code is the same as the sent code for both Procedure
+ /// steps 1 and 2
+ /// * received status message is the same as the sent message for both
+ /// Procedure steps 1 and 2
+ Future<void> statusCodeAndMessage() async {
+ final expectedStatus = new GrpcError.custom(2, 'test status message');
+ final responseStatus = new EchoStatus()
+ ..code = expectedStatus.code
+ ..message = expectedStatus.message;
+ try {
+ await client
+ .unaryCall(new SimpleRequest()..responseStatus = responseStatus);
+ throw 'Did not receive correct status code.';
+ } on GrpcError catch (e) {
+ if (e != expectedStatus) {
+ throw 'Received incorrect status: $e.';
+ }
+ }
+ Stream<StreamingOutputCallRequest> requests() async* {
+ yield new StreamingOutputCallRequest()..responseStatus = responseStatus;
+ }
+
+ try {
+ await for (final _ in client.fullDuplexCall(requests())) {
+ throw 'Received unexpected response.';
+ }
+ throw 'Did not receive correct status code.';
+ } on GrpcError catch (e) {
+ if (e != expectedStatus) {
+ throw 'Received incorrect status: $e.';
+ }
+ }
+ }
+
+ /// This test verifies that calling an unimplemented RPC method returns the
+ /// UNIMPLEMENTED status code.
+ ///
+ /// Procedure:
+ /// * Client calls `grpc.testing.TestService/UnimplementedCall` with an empty
+ /// request (defined as `grpc.testing.Empty`):
+ /// {
+ /// }
+ ///
+ /// Client asserts:
+ /// * received status code is 12 (UNIMPLEMENTED)
+ Future<void> unimplementedMethod() async {
+ try {
+ await client.unimplementedCall(new Empty());
+ throw 'Did not throw.';
+ } on GrpcError catch (e) {
+ if (e.code != StatusCode.unimplemented) {
+ throw 'Unexpected status code ${e.code} - ${e.message}.';
+ }
+ }
+ }
+
+ /// This test verifies calling an unimplemented server returns the
+ /// UNIMPLEMENTED status code.
+ ///
+ /// Procedure:
+ /// * Client calls `grpc.testing.UnimplementedService/UnimplementedCall` with
+ /// an empty request (defined as `grpc.testing.Empty`)
+ ///
+ /// Client asserts:
+ /// * received status code is 12 (UNIMPLEMENTED)
+ Future<void> unimplementedService() async {
+ try {
+ await unimplementedServiceClient.unimplementedCall(new Empty());
+ throw 'Did not throw.';
+ } on GrpcError catch (e) {
+ if (e.code != StatusCode.unimplemented) {
+ throw 'Unexpected status code ${e.code} - ${e.message}.';
+ }
+ }
+ }
+
+ /// This test verifies that a request can be cancelled after metadata has been
+ /// sent but before payloads are sent.
+ ///
+ /// Procedure:
+ /// 1. Client starts StreamingInputCall
+ /// 2. Client immediately cancels request
+ ///
+ /// Client asserts:
+ /// * Call completed with status CANCELLED
+ Future<void> cancelAfterBegin() async {
+ final requests = new StreamController<StreamingInputCallRequest>();
+ final call = client.streamingInputCall(requests.stream);
+ scheduleMicrotask(call.cancel);
+ try {
+ await call;
+ throw 'Expected exception.';
+ } on GrpcError catch (e) {
+ if (e.code != StatusCode.cancelled) {
+ throw 'Unexpected status code ${e.code} - ${e.message}';
+ }
+ }
+ requests.close();
+ }
+
+ /// This test verifies that a request can be cancelled after receiving a
+ /// message from the server.
+ ///
+ /// Procedure:
+ /// 1. Client starts FullDuplexCall with
+ /// {
+ /// response_parameters: {
+ /// size: 31415
+ /// }
+ /// payload: {
+ /// body: 27182 bytes of zeros
+ /// }
+ /// }
+ ///
+ /// 2. After receiving a response, client cancels request
+ ///
+ /// Client asserts:
+ /// * Call completed with status CANCELLED
+ Future<void> cancelAfterFirstResponse() async {
+ final requests = new StreamController<StreamingOutputCallRequest>();
+ final call = client.fullDuplexCall(requests.stream);
+ final completer = new Completer();
+
+ var receivedResponse = false;
+ call.listen((response) {
+ if (receivedResponse) {
+ completer.completeError('Received too many responses.');
+ return;
+ }
+ receivedResponse = true;
+ if (response.payload.body.length != 31415) {
+ completer.completeError('Invalid response length: '
+ '${response.payload.body.length} != 31415.');
+ }
+ call.cancel();
+ }, onError: (e) {
+ if (e is! GrpcError) completer.completeError('Unexpected error: $e.');
+ if (e.code != StatusCode.cancelled) {
+ completer
+ .completeError('Unexpected status code ${e.code}: ${e.message}.');
+ }
+ completer.complete(true);
+ }, onDone: () {
+ if (!completer.isCompleted) completer.completeError('Expected error.');
+ });
+
+ requests.add(new StreamingOutputCallRequest()
+ ..responseParameters.add(new ResponseParameters()..size = 31415)
+ ..payload = (new Payload()..body = new Uint8List(27182)));
+ await completer.future;
+ requests.close();
+ }
+
+ /// This test verifies that an RPC request whose lifetime exceeds its
+ /// configured timeout value will end with the DeadlineExceeded status.
+ ///
+ /// Procedure:
+ /// 1. Client calls FullDuplexCall with the following request and sets its
+ /// timeout to 1ms
+ /// {
+ /// payload: {
+ /// body: 27182 bytes of zeros
+ /// }
+ /// }
+ ///
+ /// 2. Client waits
+ ///
+ /// Client asserts:
+ /// * Call completed with status DEADLINE_EXCEEDED.
+ Future<void> timeoutOnSleepingServer() async {
+ final requests = new StreamController<StreamingOutputCallRequest>();
+ final call = client.fullDuplexCall(requests.stream,
+ options: new CallOptions(timeout: new Duration(milliseconds: 1)));
+ requests.add(new StreamingOutputCallRequest()
+ ..payload = (new Payload()..body = new Uint8List(27182)));
+ try {
+ await for (final _ in call) {
+ throw 'Unexpected response received.';
+ }
+ throw 'Expected exception.';
+ } on GrpcError catch (e) {
+ if (e.code != StatusCode.deadlineExceeded) {
+ throw 'Unexpected status code ${e.code} - ${e.message}.';
+ }
+ } finally {
+ requests.close();
+ }
+ }
+}
diff --git a/grpc/interop/lib/src/generated/empty.pb.dart b/grpc/interop/lib/src/generated/empty.pb.dart
new file mode 100644
index 0000000..4b040c7
--- /dev/null
+++ b/grpc/interop/lib/src/generated/empty.pb.dart
@@ -0,0 +1,36 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library grpc.testing_empty;
+
+// ignore: UNUSED_SHOWN_NAME
+import 'dart:core' show int, bool, double, String, List, override;
+
+import 'package:protobuf/protobuf.dart';
+
+class Empty extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('Empty')
+ ..hasRequiredFields = false;
+
+ Empty() : super();
+ Empty.fromBuffer(List<int> i, [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ Empty.fromJson(String i, [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ Empty clone() => new Empty()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static Empty create() => new Empty();
+ static PbList<Empty> createRepeated() => new PbList<Empty>();
+ static Empty getDefault() {
+ if (_defaultInstance == null) _defaultInstance = new _ReadonlyEmpty();
+ return _defaultInstance;
+ }
+
+ static Empty _defaultInstance;
+ static void $checkItem(Empty v) {
+ if (v is! Empty) checkItemFailed(v, 'Empty');
+ }
+}
+
+class _ReadonlyEmpty extends Empty with ReadonlyMessageMixin {}
diff --git a/grpc/interop/lib/src/generated/messages.pb.dart b/grpc/interop/lib/src/generated/messages.pb.dart
new file mode 100644
index 0000000..fe00e19
--- /dev/null
+++ b/grpc/interop/lib/src/generated/messages.pb.dart
@@ -0,0 +1,655 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library grpc.testing_messages;
+
+// ignore: UNUSED_SHOWN_NAME
+import 'dart:core' show int, bool, double, String, List, override;
+
+import 'package:protobuf/protobuf.dart';
+
+import 'messages.pbenum.dart';
+
+export 'messages.pbenum.dart';
+
+class BoolValue extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('BoolValue')
+ ..aOB(1, 'value')
+ ..hasRequiredFields = false;
+
+ BoolValue() : super();
+ BoolValue.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ BoolValue.fromJson(String i, [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ BoolValue clone() => new BoolValue()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static BoolValue create() => new BoolValue();
+ static PbList<BoolValue> createRepeated() => new PbList<BoolValue>();
+ static BoolValue getDefault() {
+ if (_defaultInstance == null) _defaultInstance = new _ReadonlyBoolValue();
+ return _defaultInstance;
+ }
+
+ static BoolValue _defaultInstance;
+ static void $checkItem(BoolValue v) {
+ if (v is! BoolValue) checkItemFailed(v, 'BoolValue');
+ }
+
+ bool get value => $_get(0, false);
+ set value(bool v) {
+ $_setBool(0, v);
+ }
+
+ bool hasValue() => $_has(0);
+ void clearValue() => clearField(1);
+}
+
+class _ReadonlyBoolValue extends BoolValue with ReadonlyMessageMixin {}
+
+class Payload extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('Payload')
+ ..e<PayloadType>(1, 'type', PbFieldType.OE, PayloadType.COMPRESSABLE,
+ PayloadType.valueOf, PayloadType.values)
+ ..a<List<int>>(2, 'body', PbFieldType.OY)
+ ..hasRequiredFields = false;
+
+ Payload() : super();
+ Payload.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ Payload.fromJson(String i, [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ Payload clone() => new Payload()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static Payload create() => new Payload();
+ static PbList<Payload> createRepeated() => new PbList<Payload>();
+ static Payload getDefault() {
+ if (_defaultInstance == null) _defaultInstance = new _ReadonlyPayload();
+ return _defaultInstance;
+ }
+
+ static Payload _defaultInstance;
+ static void $checkItem(Payload v) {
+ if (v is! Payload) checkItemFailed(v, 'Payload');
+ }
+
+ PayloadType get type => $_getN(0);
+ set type(PayloadType v) {
+ setField(1, v);
+ }
+
+ bool hasType() => $_has(0);
+ void clearType() => clearField(1);
+
+ List<int> get body => $_getN(1);
+ set body(List<int> v) {
+ $_setBytes(1, v);
+ }
+
+ bool hasBody() => $_has(1);
+ void clearBody() => clearField(2);
+}
+
+class _ReadonlyPayload extends Payload with ReadonlyMessageMixin {}
+
+class EchoStatus extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('EchoStatus')
+ ..a<int>(1, 'code', PbFieldType.O3)
+ ..aOS(2, 'message')
+ ..hasRequiredFields = false;
+
+ EchoStatus() : super();
+ EchoStatus.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ EchoStatus.fromJson(String i, [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ EchoStatus clone() => new EchoStatus()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static EchoStatus create() => new EchoStatus();
+ static PbList<EchoStatus> createRepeated() => new PbList<EchoStatus>();
+ static EchoStatus getDefault() {
+ if (_defaultInstance == null) _defaultInstance = new _ReadonlyEchoStatus();
+ return _defaultInstance;
+ }
+
+ static EchoStatus _defaultInstance;
+ static void $checkItem(EchoStatus v) {
+ if (v is! EchoStatus) checkItemFailed(v, 'EchoStatus');
+ }
+
+ int get code => $_get(0, 0);
+ set code(int v) {
+ $_setUnsignedInt32(0, v);
+ }
+
+ bool hasCode() => $_has(0);
+ void clearCode() => clearField(1);
+
+ String get message => $_getS(1, '');
+ set message(String v) {
+ $_setString(1, v);
+ }
+
+ bool hasMessage() => $_has(1);
+ void clearMessage() => clearField(2);
+}
+
+class _ReadonlyEchoStatus extends EchoStatus with ReadonlyMessageMixin {}
+
+class SimpleRequest extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('SimpleRequest')
+ ..e<PayloadType>(1, 'responseType', PbFieldType.OE,
+ PayloadType.COMPRESSABLE, PayloadType.valueOf, PayloadType.values)
+ ..a<int>(2, 'responseSize', PbFieldType.O3)
+ ..a<Payload>(
+ 3, 'payload', PbFieldType.OM, Payload.getDefault, Payload.create)
+ ..aOB(4, 'fillUsername')
+ ..aOB(5, 'fillOauthScope')
+ ..a<BoolValue>(6, 'responseCompressed', PbFieldType.OM,
+ BoolValue.getDefault, BoolValue.create)
+ ..a<EchoStatus>(7, 'responseStatus', PbFieldType.OM, EchoStatus.getDefault,
+ EchoStatus.create)
+ ..a<BoolValue>(8, 'expectCompressed', PbFieldType.OM, BoolValue.getDefault,
+ BoolValue.create)
+ ..hasRequiredFields = false;
+
+ SimpleRequest() : super();
+ SimpleRequest.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ SimpleRequest.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ SimpleRequest clone() => new SimpleRequest()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static SimpleRequest create() => new SimpleRequest();
+ static PbList<SimpleRequest> createRepeated() => new PbList<SimpleRequest>();
+ static SimpleRequest getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlySimpleRequest();
+ return _defaultInstance;
+ }
+
+ static SimpleRequest _defaultInstance;
+ static void $checkItem(SimpleRequest v) {
+ if (v is! SimpleRequest) checkItemFailed(v, 'SimpleRequest');
+ }
+
+ PayloadType get responseType => $_getN(0);
+ set responseType(PayloadType v) {
+ setField(1, v);
+ }
+
+ bool hasResponseType() => $_has(0);
+ void clearResponseType() => clearField(1);
+
+ int get responseSize => $_get(1, 0);
+ set responseSize(int v) {
+ $_setUnsignedInt32(1, v);
+ }
+
+ bool hasResponseSize() => $_has(1);
+ void clearResponseSize() => clearField(2);
+
+ Payload get payload => $_getN(2);
+ set payload(Payload v) {
+ setField(3, v);
+ }
+
+ bool hasPayload() => $_has(2);
+ void clearPayload() => clearField(3);
+
+ bool get fillUsername => $_get(3, false);
+ set fillUsername(bool v) {
+ $_setBool(3, v);
+ }
+
+ bool hasFillUsername() => $_has(3);
+ void clearFillUsername() => clearField(4);
+
+ bool get fillOauthScope => $_get(4, false);
+ set fillOauthScope(bool v) {
+ $_setBool(4, v);
+ }
+
+ bool hasFillOauthScope() => $_has(4);
+ void clearFillOauthScope() => clearField(5);
+
+ BoolValue get responseCompressed => $_getN(5);
+ set responseCompressed(BoolValue v) {
+ setField(6, v);
+ }
+
+ bool hasResponseCompressed() => $_has(5);
+ void clearResponseCompressed() => clearField(6);
+
+ EchoStatus get responseStatus => $_getN(6);
+ set responseStatus(EchoStatus v) {
+ setField(7, v);
+ }
+
+ bool hasResponseStatus() => $_has(6);
+ void clearResponseStatus() => clearField(7);
+
+ BoolValue get expectCompressed => $_getN(7);
+ set expectCompressed(BoolValue v) {
+ setField(8, v);
+ }
+
+ bool hasExpectCompressed() => $_has(7);
+ void clearExpectCompressed() => clearField(8);
+}
+
+class _ReadonlySimpleRequest extends SimpleRequest with ReadonlyMessageMixin {}
+
+class SimpleResponse extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('SimpleResponse')
+ ..a<Payload>(
+ 1, 'payload', PbFieldType.OM, Payload.getDefault, Payload.create)
+ ..aOS(2, 'username')
+ ..aOS(3, 'oauthScope')
+ ..hasRequiredFields = false;
+
+ SimpleResponse() : super();
+ SimpleResponse.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ SimpleResponse.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ SimpleResponse clone() => new SimpleResponse()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static SimpleResponse create() => new SimpleResponse();
+ static PbList<SimpleResponse> createRepeated() =>
+ new PbList<SimpleResponse>();
+ static SimpleResponse getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlySimpleResponse();
+ return _defaultInstance;
+ }
+
+ static SimpleResponse _defaultInstance;
+ static void $checkItem(SimpleResponse v) {
+ if (v is! SimpleResponse) checkItemFailed(v, 'SimpleResponse');
+ }
+
+ Payload get payload => $_getN(0);
+ set payload(Payload v) {
+ setField(1, v);
+ }
+
+ bool hasPayload() => $_has(0);
+ void clearPayload() => clearField(1);
+
+ String get username => $_getS(1, '');
+ set username(String v) {
+ $_setString(1, v);
+ }
+
+ bool hasUsername() => $_has(1);
+ void clearUsername() => clearField(2);
+
+ String get oauthScope => $_getS(2, '');
+ set oauthScope(String v) {
+ $_setString(2, v);
+ }
+
+ bool hasOauthScope() => $_has(2);
+ void clearOauthScope() => clearField(3);
+}
+
+class _ReadonlySimpleResponse extends SimpleResponse with ReadonlyMessageMixin {
+}
+
+class StreamingInputCallRequest extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('StreamingInputCallRequest')
+ ..a<Payload>(
+ 1, 'payload', PbFieldType.OM, Payload.getDefault, Payload.create)
+ ..a<BoolValue>(2, 'expectCompressed', PbFieldType.OM, BoolValue.getDefault,
+ BoolValue.create)
+ ..hasRequiredFields = false;
+
+ StreamingInputCallRequest() : super();
+ StreamingInputCallRequest.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ StreamingInputCallRequest.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ StreamingInputCallRequest clone() =>
+ new StreamingInputCallRequest()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static StreamingInputCallRequest create() => new StreamingInputCallRequest();
+ static PbList<StreamingInputCallRequest> createRepeated() =>
+ new PbList<StreamingInputCallRequest>();
+ static StreamingInputCallRequest getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlyStreamingInputCallRequest();
+ return _defaultInstance;
+ }
+
+ static StreamingInputCallRequest _defaultInstance;
+ static void $checkItem(StreamingInputCallRequest v) {
+ if (v is! StreamingInputCallRequest)
+ checkItemFailed(v, 'StreamingInputCallRequest');
+ }
+
+ Payload get payload => $_getN(0);
+ set payload(Payload v) {
+ setField(1, v);
+ }
+
+ bool hasPayload() => $_has(0);
+ void clearPayload() => clearField(1);
+
+ BoolValue get expectCompressed => $_getN(1);
+ set expectCompressed(BoolValue v) {
+ setField(2, v);
+ }
+
+ bool hasExpectCompressed() => $_has(1);
+ void clearExpectCompressed() => clearField(2);
+}
+
+class _ReadonlyStreamingInputCallRequest extends StreamingInputCallRequest
+ with ReadonlyMessageMixin {}
+
+class StreamingInputCallResponse extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('StreamingInputCallResponse')
+ ..a<int>(1, 'aggregatedPayloadSize', PbFieldType.O3)
+ ..hasRequiredFields = false;
+
+ StreamingInputCallResponse() : super();
+ StreamingInputCallResponse.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ StreamingInputCallResponse.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ StreamingInputCallResponse clone() =>
+ new StreamingInputCallResponse()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static StreamingInputCallResponse create() =>
+ new StreamingInputCallResponse();
+ static PbList<StreamingInputCallResponse> createRepeated() =>
+ new PbList<StreamingInputCallResponse>();
+ static StreamingInputCallResponse getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlyStreamingInputCallResponse();
+ return _defaultInstance;
+ }
+
+ static StreamingInputCallResponse _defaultInstance;
+ static void $checkItem(StreamingInputCallResponse v) {
+ if (v is! StreamingInputCallResponse)
+ checkItemFailed(v, 'StreamingInputCallResponse');
+ }
+
+ int get aggregatedPayloadSize => $_get(0, 0);
+ set aggregatedPayloadSize(int v) {
+ $_setUnsignedInt32(0, v);
+ }
+
+ bool hasAggregatedPayloadSize() => $_has(0);
+ void clearAggregatedPayloadSize() => clearField(1);
+}
+
+class _ReadonlyStreamingInputCallResponse extends StreamingInputCallResponse
+ with ReadonlyMessageMixin {}
+
+class ResponseParameters extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('ResponseParameters')
+ ..a<int>(1, 'size', PbFieldType.O3)
+ ..a<int>(2, 'intervalUs', PbFieldType.O3)
+ ..a<BoolValue>(
+ 3, 'compressed', PbFieldType.OM, BoolValue.getDefault, BoolValue.create)
+ ..hasRequiredFields = false;
+
+ ResponseParameters() : super();
+ ResponseParameters.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ ResponseParameters.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ ResponseParameters clone() =>
+ new ResponseParameters()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static ResponseParameters create() => new ResponseParameters();
+ static PbList<ResponseParameters> createRepeated() =>
+ new PbList<ResponseParameters>();
+ static ResponseParameters getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlyResponseParameters();
+ return _defaultInstance;
+ }
+
+ static ResponseParameters _defaultInstance;
+ static void $checkItem(ResponseParameters v) {
+ if (v is! ResponseParameters) checkItemFailed(v, 'ResponseParameters');
+ }
+
+ int get size => $_get(0, 0);
+ set size(int v) {
+ $_setUnsignedInt32(0, v);
+ }
+
+ bool hasSize() => $_has(0);
+ void clearSize() => clearField(1);
+
+ int get intervalUs => $_get(1, 0);
+ set intervalUs(int v) {
+ $_setUnsignedInt32(1, v);
+ }
+
+ bool hasIntervalUs() => $_has(1);
+ void clearIntervalUs() => clearField(2);
+
+ BoolValue get compressed => $_getN(2);
+ set compressed(BoolValue v) {
+ setField(3, v);
+ }
+
+ bool hasCompressed() => $_has(2);
+ void clearCompressed() => clearField(3);
+}
+
+class _ReadonlyResponseParameters extends ResponseParameters
+ with ReadonlyMessageMixin {}
+
+class StreamingOutputCallRequest extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('StreamingOutputCallRequest')
+ ..e<PayloadType>(1, 'responseType', PbFieldType.OE,
+ PayloadType.COMPRESSABLE, PayloadType.valueOf, PayloadType.values)
+ ..pp<ResponseParameters>(2, 'responseParameters', PbFieldType.PM,
+ ResponseParameters.$checkItem, ResponseParameters.create)
+ ..a<Payload>(
+ 3, 'payload', PbFieldType.OM, Payload.getDefault, Payload.create)
+ ..a<EchoStatus>(7, 'responseStatus', PbFieldType.OM, EchoStatus.getDefault,
+ EchoStatus.create)
+ ..hasRequiredFields = false;
+
+ StreamingOutputCallRequest() : super();
+ StreamingOutputCallRequest.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ StreamingOutputCallRequest.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ StreamingOutputCallRequest clone() =>
+ new StreamingOutputCallRequest()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static StreamingOutputCallRequest create() =>
+ new StreamingOutputCallRequest();
+ static PbList<StreamingOutputCallRequest> createRepeated() =>
+ new PbList<StreamingOutputCallRequest>();
+ static StreamingOutputCallRequest getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlyStreamingOutputCallRequest();
+ return _defaultInstance;
+ }
+
+ static StreamingOutputCallRequest _defaultInstance;
+ static void $checkItem(StreamingOutputCallRequest v) {
+ if (v is! StreamingOutputCallRequest)
+ checkItemFailed(v, 'StreamingOutputCallRequest');
+ }
+
+ PayloadType get responseType => $_getN(0);
+ set responseType(PayloadType v) {
+ setField(1, v);
+ }
+
+ bool hasResponseType() => $_has(0);
+ void clearResponseType() => clearField(1);
+
+ List<ResponseParameters> get responseParameters => $_getList(1);
+
+ Payload get payload => $_getN(2);
+ set payload(Payload v) {
+ setField(3, v);
+ }
+
+ bool hasPayload() => $_has(2);
+ void clearPayload() => clearField(3);
+
+ EchoStatus get responseStatus => $_getN(3);
+ set responseStatus(EchoStatus v) {
+ setField(7, v);
+ }
+
+ bool hasResponseStatus() => $_has(3);
+ void clearResponseStatus() => clearField(7);
+}
+
+class _ReadonlyStreamingOutputCallRequest extends StreamingOutputCallRequest
+ with ReadonlyMessageMixin {}
+
+class StreamingOutputCallResponse extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('StreamingOutputCallResponse')
+ ..a<Payload>(
+ 1, 'payload', PbFieldType.OM, Payload.getDefault, Payload.create)
+ ..hasRequiredFields = false;
+
+ StreamingOutputCallResponse() : super();
+ StreamingOutputCallResponse.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ StreamingOutputCallResponse.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ StreamingOutputCallResponse clone() =>
+ new StreamingOutputCallResponse()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static StreamingOutputCallResponse create() =>
+ new StreamingOutputCallResponse();
+ static PbList<StreamingOutputCallResponse> createRepeated() =>
+ new PbList<StreamingOutputCallResponse>();
+ static StreamingOutputCallResponse getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlyStreamingOutputCallResponse();
+ return _defaultInstance;
+ }
+
+ static StreamingOutputCallResponse _defaultInstance;
+ static void $checkItem(StreamingOutputCallResponse v) {
+ if (v is! StreamingOutputCallResponse)
+ checkItemFailed(v, 'StreamingOutputCallResponse');
+ }
+
+ Payload get payload => $_getN(0);
+ set payload(Payload v) {
+ setField(1, v);
+ }
+
+ bool hasPayload() => $_has(0);
+ void clearPayload() => clearField(1);
+}
+
+class _ReadonlyStreamingOutputCallResponse extends StreamingOutputCallResponse
+ with ReadonlyMessageMixin {}
+
+class ReconnectParams extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('ReconnectParams')
+ ..a<int>(1, 'maxReconnectBackoffMs', PbFieldType.O3)
+ ..hasRequiredFields = false;
+
+ ReconnectParams() : super();
+ ReconnectParams.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ ReconnectParams.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ ReconnectParams clone() => new ReconnectParams()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static ReconnectParams create() => new ReconnectParams();
+ static PbList<ReconnectParams> createRepeated() =>
+ new PbList<ReconnectParams>();
+ static ReconnectParams getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlyReconnectParams();
+ return _defaultInstance;
+ }
+
+ static ReconnectParams _defaultInstance;
+ static void $checkItem(ReconnectParams v) {
+ if (v is! ReconnectParams) checkItemFailed(v, 'ReconnectParams');
+ }
+
+ int get maxReconnectBackoffMs => $_get(0, 0);
+ set maxReconnectBackoffMs(int v) {
+ $_setUnsignedInt32(0, v);
+ }
+
+ bool hasMaxReconnectBackoffMs() => $_has(0);
+ void clearMaxReconnectBackoffMs() => clearField(1);
+}
+
+class _ReadonlyReconnectParams extends ReconnectParams
+ with ReadonlyMessageMixin {}
+
+class ReconnectInfo extends GeneratedMessage {
+ static final BuilderInfo _i = new BuilderInfo('ReconnectInfo')
+ ..aOB(1, 'passed')
+ ..p<int>(2, 'backoffMs', PbFieldType.P3)
+ ..hasRequiredFields = false;
+
+ ReconnectInfo() : super();
+ ReconnectInfo.fromBuffer(List<int> i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromBuffer(i, r);
+ ReconnectInfo.fromJson(String i,
+ [ExtensionRegistry r = ExtensionRegistry.EMPTY])
+ : super.fromJson(i, r);
+ ReconnectInfo clone() => new ReconnectInfo()..mergeFromMessage(this);
+ BuilderInfo get info_ => _i;
+ static ReconnectInfo create() => new ReconnectInfo();
+ static PbList<ReconnectInfo> createRepeated() => new PbList<ReconnectInfo>();
+ static ReconnectInfo getDefault() {
+ if (_defaultInstance == null)
+ _defaultInstance = new _ReadonlyReconnectInfo();
+ return _defaultInstance;
+ }
+
+ static ReconnectInfo _defaultInstance;
+ static void $checkItem(ReconnectInfo v) {
+ if (v is! ReconnectInfo) checkItemFailed(v, 'ReconnectInfo');
+ }
+
+ bool get passed => $_get(0, false);
+ set passed(bool v) {
+ $_setBool(0, v);
+ }
+
+ bool hasPassed() => $_has(0);
+ void clearPassed() => clearField(1);
+
+ List<int> get backoffMs => $_getList(1);
+}
+
+class _ReadonlyReconnectInfo extends ReconnectInfo with ReadonlyMessageMixin {}
diff --git a/grpc/interop/lib/src/generated/messages.pbenum.dart b/grpc/interop/lib/src/generated/messages.pbenum.dart
new file mode 100644
index 0000000..964d000
--- /dev/null
+++ b/grpc/interop/lib/src/generated/messages.pbenum.dart
@@ -0,0 +1,26 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library grpc.testing_messages_pbenum;
+
+// ignore_for_file: UNDEFINED_SHOWN_NAME,UNUSED_SHOWN_NAME
+import 'dart:core' show int, dynamic, String, List, Map;
+import 'package:protobuf/protobuf.dart';
+
+class PayloadType extends ProtobufEnum {
+ static const PayloadType COMPRESSABLE =
+ const PayloadType._(0, 'COMPRESSABLE');
+
+ static const List<PayloadType> values = const <PayloadType>[
+ COMPRESSABLE,
+ ];
+
+ static final Map<int, dynamic> _byValue = ProtobufEnum.initByValue(values);
+ static PayloadType valueOf(int value) => _byValue[value] as PayloadType;
+ static void $checkItem(PayloadType v) {
+ if (v is! PayloadType) checkItemFailed(v, 'PayloadType');
+ }
+
+ const PayloadType._(int v, String n) : super(v, n);
+}
diff --git a/grpc/interop/lib/src/generated/test.pb.dart b/grpc/interop/lib/src/generated/test.pb.dart
new file mode 100644
index 0000000..d64b754
--- /dev/null
+++ b/grpc/interop/lib/src/generated/test.pb.dart
@@ -0,0 +1,8 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library grpc.testing_test;
+
+// ignore: UNUSED_SHOWN_NAME
+import 'dart:core' show int, bool, double, String, List, override;
diff --git a/grpc/interop/lib/src/generated/test.pbgrpc.dart b/grpc/interop/lib/src/generated/test.pbgrpc.dart
new file mode 100644
index 0000000..f7ba4db
--- /dev/null
+++ b/grpc/interop/lib/src/generated/test.pbgrpc.dart
@@ -0,0 +1,307 @@
+///
+// Generated code. Do not modify.
+///
+// ignore_for_file: non_constant_identifier_names,library_prefixes
+library grpc.testing_test_pbgrpc;
+
+import 'dart:async';
+
+import 'package:grpc/grpc.dart';
+
+import 'empty.pb.dart';
+import 'messages.pb.dart';
+export 'test.pb.dart';
+
+class TestServiceClient extends Client {
+ static final _$emptyCall = new ClientMethod<Empty, Empty>(
+ '/grpc.testing.TestService/EmptyCall',
+ (Empty value) => value.writeToBuffer(),
+ (List<int> value) => new Empty.fromBuffer(value));
+ static final _$unaryCall = new ClientMethod<SimpleRequest, SimpleResponse>(
+ '/grpc.testing.TestService/UnaryCall',
+ (SimpleRequest value) => value.writeToBuffer(),
+ (List<int> value) => new SimpleResponse.fromBuffer(value));
+ static final _$cacheableUnaryCall =
+ new ClientMethod<SimpleRequest, SimpleResponse>(
+ '/grpc.testing.TestService/CacheableUnaryCall',
+ (SimpleRequest value) => value.writeToBuffer(),
+ (List<int> value) => new SimpleResponse.fromBuffer(value));
+ static final _$streamingOutputCall =
+ new ClientMethod<StreamingOutputCallRequest, StreamingOutputCallResponse>(
+ '/grpc.testing.TestService/StreamingOutputCall',
+ (StreamingOutputCallRequest value) => value.writeToBuffer(),
+ (List<int> value) =>
+ new StreamingOutputCallResponse.fromBuffer(value));
+ static final _$streamingInputCall =
+ new ClientMethod<StreamingInputCallRequest, StreamingInputCallResponse>(
+ '/grpc.testing.TestService/StreamingInputCall',
+ (StreamingInputCallRequest value) => value.writeToBuffer(),
+ (List<int> value) =>
+ new StreamingInputCallResponse.fromBuffer(value));
+ static final _$fullDuplexCall =
+ new ClientMethod<StreamingOutputCallRequest, StreamingOutputCallResponse>(
+ '/grpc.testing.TestService/FullDuplexCall',
+ (StreamingOutputCallRequest value) => value.writeToBuffer(),
+ (List<int> value) =>
+ new StreamingOutputCallResponse.fromBuffer(value));
+ static final _$halfDuplexCall =
+ new ClientMethod<StreamingOutputCallRequest, StreamingOutputCallResponse>(
+ '/grpc.testing.TestService/HalfDuplexCall',
+ (StreamingOutputCallRequest value) => value.writeToBuffer(),
+ (List<int> value) =>
+ new StreamingOutputCallResponse.fromBuffer(value));
+ static final _$unimplementedCall = new ClientMethod<Empty, Empty>(
+ '/grpc.testing.TestService/UnimplementedCall',
+ (Empty value) => value.writeToBuffer(),
+ (List<int> value) => new Empty.fromBuffer(value));
+
+ TestServiceClient(ClientChannel channel, {CallOptions options})
+ : super(channel, options: options);
+
+ ResponseFuture<Empty> emptyCall(Empty request, {CallOptions options}) {
+ final call = $createCall(_$emptyCall, new Stream.fromIterable([request]),
+ options: options);
+ return new ResponseFuture(call);
+ }
+
+ ResponseFuture<SimpleResponse> unaryCall(SimpleRequest request,
+ {CallOptions options}) {
+ final call = $createCall(_$unaryCall, new Stream.fromIterable([request]),
+ options: options);
+ return new ResponseFuture(call);
+ }
+
+ ResponseFuture<SimpleResponse> cacheableUnaryCall(SimpleRequest request,
+ {CallOptions options}) {
+ final call = $createCall(
+ _$cacheableUnaryCall, new Stream.fromIterable([request]),
+ options: options);
+ return new ResponseFuture(call);
+ }
+
+ ResponseStream<StreamingOutputCallResponse> streamingOutputCall(
+ StreamingOutputCallRequest request,
+ {CallOptions options}) {
+ final call = $createCall(
+ _$streamingOutputCall, new Stream.fromIterable([request]),
+ options: options);
+ return new ResponseStream(call);
+ }
+
+ ResponseFuture<StreamingInputCallResponse> streamingInputCall(
+ Stream<StreamingInputCallRequest> request,
+ {CallOptions options}) {
+ final call = $createCall(_$streamingInputCall, request, options: options);
+ return new ResponseFuture(call);
+ }
+
+ ResponseStream<StreamingOutputCallResponse> fullDuplexCall(
+ Stream<StreamingOutputCallRequest> request,
+ {CallOptions options}) {
+ final call = $createCall(_$fullDuplexCall, request, options: options);
+ return new ResponseStream(call);
+ }
+
+ ResponseStream<StreamingOutputCallResponse> halfDuplexCall(
+ Stream<StreamingOutputCallRequest> request,
+ {CallOptions options}) {
+ final call = $createCall(_$halfDuplexCall, request, options: options);
+ return new ResponseStream(call);
+ }
+
+ ResponseFuture<Empty> unimplementedCall(Empty request,
+ {CallOptions options}) {
+ final call = $createCall(
+ _$unimplementedCall, new Stream.fromIterable([request]),
+ options: options);
+ return new ResponseFuture(call);
+ }
+}
+
+abstract class TestServiceBase extends Service {
+ String get $name => 'grpc.testing.TestService';
+
+ TestServiceBase() {
+ $addMethod(new ServiceMethod<Empty, Empty>(
+ 'EmptyCall',
+ emptyCall_Pre,
+ false,
+ false,
+ (List<int> value) => new Empty.fromBuffer(value),
+ (Empty value) => value.writeToBuffer()));
+ $addMethod(new ServiceMethod<SimpleRequest, SimpleResponse>(
+ 'UnaryCall',
+ unaryCall_Pre,
+ false,
+ false,
+ (List<int> value) => new SimpleRequest.fromBuffer(value),
+ (SimpleResponse value) => value.writeToBuffer()));
+ $addMethod(new ServiceMethod<SimpleRequest, SimpleResponse>(
+ 'CacheableUnaryCall',
+ cacheableUnaryCall_Pre,
+ false,
+ false,
+ (List<int> value) => new SimpleRequest.fromBuffer(value),
+ (SimpleResponse value) => value.writeToBuffer()));
+ $addMethod(new ServiceMethod<StreamingOutputCallRequest,
+ StreamingOutputCallResponse>(
+ 'StreamingOutputCall',
+ streamingOutputCall_Pre,
+ false,
+ true,
+ (List<int> value) => new StreamingOutputCallRequest.fromBuffer(value),
+ (StreamingOutputCallResponse value) => value.writeToBuffer()));
+ $addMethod(new ServiceMethod<StreamingInputCallRequest,
+ StreamingInputCallResponse>(
+ 'StreamingInputCall',
+ streamingInputCall,
+ true,
+ false,
+ (List<int> value) => new StreamingInputCallRequest.fromBuffer(value),
+ (StreamingInputCallResponse value) => value.writeToBuffer()));
+ $addMethod(new ServiceMethod<StreamingOutputCallRequest,
+ StreamingOutputCallResponse>(
+ 'FullDuplexCall',
+ fullDuplexCall,
+ true,
+ true,
+ (List<int> value) => new StreamingOutputCallRequest.fromBuffer(value),
+ (StreamingOutputCallResponse value) => value.writeToBuffer()));
+ $addMethod(new ServiceMethod<StreamingOutputCallRequest,
+ StreamingOutputCallResponse>(
+ 'HalfDuplexCall',
+ halfDuplexCall,
+ true,
+ true,
+ (List<int> value) => new StreamingOutputCallRequest.fromBuffer(value),
+ (StreamingOutputCallResponse value) => value.writeToBuffer()));
+ }
+
+ Future<Empty> emptyCall_Pre(ServiceCall call, Future request) async {
+ return emptyCall(call, await request);
+ }
+
+ Future<SimpleResponse> unaryCall_Pre(ServiceCall call, Future request) async {
+ return unaryCall(call, await request);
+ }
+
+ Future<SimpleResponse> cacheableUnaryCall_Pre(
+ ServiceCall call, Future request) async {
+ return cacheableUnaryCall(call, await request);
+ }
+
+ Stream<StreamingOutputCallResponse> streamingOutputCall_Pre(
+ ServiceCall call, Future request) async* {
+ yield* streamingOutputCall(
+ call, (await request) as StreamingOutputCallRequest);
+ }
+
+ Future<Empty> emptyCall(ServiceCall call, Empty request);
+ Future<SimpleResponse> unaryCall(ServiceCall call, SimpleRequest request);
+ Future<SimpleResponse> cacheableUnaryCall(
+ ServiceCall call, SimpleRequest request);
+ Stream<StreamingOutputCallResponse> streamingOutputCall(
+ ServiceCall call, StreamingOutputCallRequest request);
+ Future<StreamingInputCallResponse> streamingInputCall(
+ ServiceCall call, Stream<StreamingInputCallRequest> request);
+ Stream<StreamingOutputCallResponse> fullDuplexCall(
+ ServiceCall call, Stream<StreamingOutputCallRequest> request);
+ Stream<StreamingOutputCallResponse> halfDuplexCall(
+ ServiceCall call, Stream<StreamingOutputCallRequest> request);
+}
+
+class UnimplementedServiceClient extends Client {
+ static final _$unimplementedCall = new ClientMethod<Empty, Empty>(
+ '/grpc.testing.UnimplementedService/UnimplementedCall',
+ (Empty value) => value.writeToBuffer(),
+ (List<int> value) => new Empty.fromBuffer(value));
+
+ UnimplementedServiceClient(ClientChannel channel, {CallOptions options})
+ : super(channel, options: options);
+
+ ResponseFuture<Empty> unimplementedCall(Empty request,
+ {CallOptions options}) {
+ final call = $createCall(
+ _$unimplementedCall, new Stream.fromIterable([request]),
+ options: options);
+ return new ResponseFuture(call);
+ }
+}
+
+abstract class UnimplementedServiceBase extends Service {
+ String get $name => 'grpc.testing.UnimplementedService';
+
+ UnimplementedServiceBase() {
+ $addMethod(new ServiceMethod<Empty, Empty>(
+ 'UnimplementedCall',
+ unimplementedCall_Pre,
+ false,
+ false,
+ (List<int> value) => new Empty.fromBuffer(value),
+ (Empty value) => value.writeToBuffer()));
+ }
+
+ Future<Empty> unimplementedCall_Pre(ServiceCall call, Future request) async {
+ return unimplementedCall(call, await request);
+ }
+
+ Future<Empty> unimplementedCall(ServiceCall call, Empty request);
+}
+
+class ReconnectServiceClient extends Client {
+ static final _$start = new ClientMethod<ReconnectParams, Empty>(
+ '/grpc.testing.ReconnectService/Start',
+ (ReconnectParams value) => value.writeToBuffer(),
+ (List<int> value) => new Empty.fromBuffer(value));
+ static final _$stop = new ClientMethod<Empty, ReconnectInfo>(
+ '/grpc.testing.ReconnectService/Stop',
+ (Empty value) => value.writeToBuffer(),
+ (List<int> value) => new ReconnectInfo.fromBuffer(value));
+
+ ReconnectServiceClient(ClientChannel channel, {CallOptions options})
+ : super(channel, options: options);
+
+ ResponseFuture<Empty> start(ReconnectParams request, {CallOptions options}) {
+ final call = $createCall(_$start, new Stream.fromIterable([request]),
+ options: options);
+ return new ResponseFuture(call);
+ }
+
+ ResponseFuture<ReconnectInfo> stop(Empty request, {CallOptions options}) {
+ final call = $createCall(_$stop, new Stream.fromIterable([request]),
+ options: options);
+ return new ResponseFuture(call);
+ }
+}
+
+abstract class ReconnectServiceBase extends Service {
+ String get $name => 'grpc.testing.ReconnectService';
+
+ ReconnectServiceBase() {
+ $addMethod(new ServiceMethod<ReconnectParams, Empty>(
+ 'Start',
+ start_Pre,
+ false,
+ false,
+ (List<int> value) => new ReconnectParams.fromBuffer(value),
+ (Empty value) => value.writeToBuffer()));
+ $addMethod(new ServiceMethod<Empty, ReconnectInfo>(
+ 'Stop',
+ stop_Pre,
+ false,
+ false,
+ (List<int> value) => new Empty.fromBuffer(value),
+ (ReconnectInfo value) => value.writeToBuffer()));
+ }
+
+ Future<Empty> start_Pre(ServiceCall call, Future request) async {
+ return start(call, await request);
+ }
+
+ Future<ReconnectInfo> stop_Pre(ServiceCall call, Future request) async {
+ return stop(call, await request);
+ }
+
+ Future<Empty> start(ServiceCall call, ReconnectParams request);
+ Future<ReconnectInfo> stop(ServiceCall call, Empty request);
+}
diff --git a/grpc/interop/protos/empty.proto b/grpc/interop/protos/empty.proto
new file mode 100644
index 0000000..6d0eb93
--- /dev/null
+++ b/grpc/interop/protos/empty.proto
@@ -0,0 +1,43 @@
+
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+syntax = "proto3";
+
+package grpc.testing;
+
+// An empty message that you can re-use to avoid defining duplicated empty
+// messages in your project. A typical example is to use it as argument or the
+// return value of a service API. For instance:
+//
+// service Foo {
+// rpc Bar (grpc.testing.Empty) returns (grpc.testing.Empty) { };
+// };
+//
+message Empty {}
diff --git a/grpc/interop/protos/messages.proto b/grpc/interop/protos/messages.proto
new file mode 100644
index 0000000..a14922a
--- /dev/null
+++ b/grpc/interop/protos/messages.proto
@@ -0,0 +1,184 @@
+
+// Copyright 2015-2016, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// Message definitions to be used by integration test service definitions.
+
+syntax = "proto3";
+
+package grpc.testing;
+
+// TODO(dgq): Go back to using well-known types once
+// https://github.com/grpc/grpc/issues/6980 has been fixed.
+// import "google/protobuf/wrappers.proto";
+message BoolValue {
+ // The bool value.
+ bool value = 1;
+}
+
+// DEPRECATED, don't use. To be removed shortly.
+// The type of payload that should be returned.
+enum PayloadType {
+ // Compressable text format.
+ COMPRESSABLE = 0;
+}
+
+// A block of data, to simply increase gRPC message size.
+message Payload {
+ // DEPRECATED, don't use. To be removed shortly.
+ // The type of data in body.
+ PayloadType type = 1;
+ // Primary contents of payload.
+ bytes body = 2;
+}
+
+// A protobuf representation for grpc status. This is used by test
+// clients to specify a status that the server should attempt to return.
+message EchoStatus {
+ int32 code = 1;
+ string message = 2;
+}
+
+// Unary request.
+message SimpleRequest {
+ // DEPRECATED, don't use. To be removed shortly.
+ // Desired payload type in the response from the server.
+ // If response_type is RANDOM, server randomly chooses one from other formats.
+ PayloadType response_type = 1;
+
+ // Desired payload size in the response from the server.
+ int32 response_size = 2;
+
+ // Optional input payload sent along with the request.
+ Payload payload = 3;
+
+ // Whether SimpleResponse should include username.
+ bool fill_username = 4;
+
+ // Whether SimpleResponse should include OAuth scope.
+ bool fill_oauth_scope = 5;
+
+ // Whether to request the server to compress the response. This field is
+ // "nullable" in order to interoperate seamlessly with clients not able to
+ // implement the full compression tests by introspecting the call to verify
+ // the response's compression status.
+ BoolValue response_compressed = 6;
+
+ // Whether server should return a given status
+ EchoStatus response_status = 7;
+
+ // Whether the server should expect this request to be compressed.
+ BoolValue expect_compressed = 8;
+}
+
+// Unary response, as configured by the request.
+message SimpleResponse {
+ // Payload to increase message size.
+ Payload payload = 1;
+ // The user the request came from, for verifying authentication was
+ // successful when the client expected it.
+ string username = 2;
+ // OAuth scope.
+ string oauth_scope = 3;
+}
+
+// Client-streaming request.
+message StreamingInputCallRequest {
+ // Optional input payload sent along with the request.
+ Payload payload = 1;
+
+ // Whether the server should expect this request to be compressed. This field
+ // is "nullable" in order to interoperate seamlessly with servers not able to
+ // implement the full compression tests by introspecting the call to verify
+ // the request's compression status.
+ BoolValue expect_compressed = 2;
+
+ // Not expecting any payload from the response.
+}
+
+// Client-streaming response.
+message StreamingInputCallResponse {
+ // Aggregated size of payloads received from the client.
+ int32 aggregated_payload_size = 1;
+}
+
+// Configuration for a particular response.
+message ResponseParameters {
+ // Desired payload sizes in responses from the server.
+ int32 size = 1;
+
+ // Desired interval between consecutive responses in the response stream in
+ // microseconds.
+ int32 interval_us = 2;
+
+ // Whether to request the server to compress the response. This field is
+ // "nullable" in order to interoperate seamlessly with clients not able to
+ // implement the full compression tests by introspecting the call to verify
+ // the response's compression status.
+ BoolValue compressed = 3;
+}
+
+// Server-streaming request.
+message StreamingOutputCallRequest {
+ // DEPRECATED, don't use. To be removed shortly.
+ // Desired payload type in the response from the server.
+ // If response_type is RANDOM, the payload from each response in the stream
+ // might be of different types. This is to simulate a mixed type of payload
+ // stream.
+ PayloadType response_type = 1;
+
+ // Configuration for each expected response message.
+ repeated ResponseParameters response_parameters = 2;
+
+ // Optional input payload sent along with the request.
+ Payload payload = 3;
+
+ // Whether server should return a given status.
+ EchoStatus response_status = 7;
+}
+
+// Server-streaming response, as configured by the request and parameters.
+message StreamingOutputCallResponse {
+ // Payload to increase response size.
+ Payload payload = 1;
+}
+
+// For reconnect interop test only.
+// Client tells server what reconnection parameters it used.
+message ReconnectParams {
+ int32 max_reconnect_backoff_ms = 1;
+}
+
+// For reconnect interop test only.
+// Server tells client whether its reconnects are following the spec and the
+// reconnect backoffs it saw.
+message ReconnectInfo {
+ bool passed = 1;
+ repeated int32 backoff_ms = 2;
+}
diff --git a/grpc/interop/protos/test.proto b/grpc/interop/protos/test.proto
new file mode 100644
index 0000000..202276b
--- /dev/null
+++ b/grpc/interop/protos/test.proto
@@ -0,0 +1,94 @@
+
+// Copyright 2015-2016, Google Inc.
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+// An integration test service that covers all the method signature permutations
+// of unary/streaming requests/responses.
+
+syntax = "proto3";
+
+import "empty.proto";
+import "messages.proto";
+
+package grpc.testing;
+
+// A simple service to test the various types of RPCs and experiment with
+// performance with various types of payload.
+service TestService {
+ // One empty request followed by one empty response.
+ rpc EmptyCall(grpc.testing.Empty) returns (grpc.testing.Empty);
+
+ // One request followed by one response.
+ rpc UnaryCall(SimpleRequest) returns (SimpleResponse);
+
+ // One request followed by one response. Response has cache control
+ // headers set such that a caching HTTP proxy (such as GFE) can
+ // satisfy subsequent requests.
+ rpc CacheableUnaryCall(SimpleRequest) returns (SimpleResponse);
+
+ // One request followed by a sequence of responses (streamed download).
+ // The server returns the payload with client desired type and sizes.
+ rpc StreamingOutputCall(StreamingOutputCallRequest)
+ returns (stream StreamingOutputCallResponse);
+
+ // A sequence of requests followed by one response (streamed upload).
+ // The server returns the aggregated size of client payload as the result.
+ rpc StreamingInputCall(stream StreamingInputCallRequest)
+ returns (StreamingInputCallResponse);
+
+ // A sequence of requests with each request served by the server immediately.
+ // As one request could lead to multiple responses, this interface
+ // demonstrates the idea of full duplexing.
+ rpc FullDuplexCall(stream StreamingOutputCallRequest)
+ returns (stream StreamingOutputCallResponse);
+
+ // A sequence of requests followed by a sequence of responses.
+ // The server buffers all the client requests and then serves them in order. A
+ // stream of responses are returned to the client when the server starts with
+ // first request.
+ rpc HalfDuplexCall(stream StreamingOutputCallRequest)
+ returns (stream StreamingOutputCallResponse);
+
+ // The test server will not implement this method. It will be used
+ // to test the behavior when clients call unimplemented methods.
+ rpc UnimplementedCall(grpc.testing.Empty) returns (grpc.testing.Empty);
+}
+
+// A simple service NOT implemented at servers so clients can test for
+// that case.
+service UnimplementedService {
+ // A call that no server should implement
+ rpc UnimplementedCall(grpc.testing.Empty) returns (grpc.testing.Empty);
+}
+
+// A service used to control reconnect server.
+service ReconnectService {
+ rpc Start(grpc.testing.ReconnectParams) returns (grpc.testing.Empty);
+ rpc Stop(grpc.testing.Empty) returns (grpc.testing.ReconnectInfo);
+}
diff --git a/grpc/interop/pubspec.yaml b/grpc/interop/pubspec.yaml
new file mode 100644
index 0000000..9432b81
--- /dev/null
+++ b/grpc/interop/pubspec.yaml
@@ -0,0 +1,17 @@
+name: interop
+description: Dart gRPC interoperability test suite.
+homepage: https://github.com/dart-lang/grpc-dart
+
+environment:
+ sdk: '>=2.0.0 <3.0.0'
+
+dependencies:
+ args: ^1.5.0
+ async: '>=1.13.3 <3.0.0'
+ collection: ^1.14.2
+ grpc:
+ path: ../
+ protobuf: ^0.10.1
+
+dev_dependencies:
+ test: ^1.3.0
diff --git a/grpc/interop/server1.key b/grpc/interop/server1.key
new file mode 100644
index 0000000..143a5b8
--- /dev/null
+++ b/grpc/interop/server1.key
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAOHDFScoLCVJpYDD
+M4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1BgzkWF+slf
+3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd9N8YwbBY
+AckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAECgYAn7qGnM2vbjJNBm0VZCkOkTIWm
+V10okw7EPJrdL2mkre9NasghNXbE1y5zDshx5Nt3KsazKOxTT8d0Jwh/3KbaN+YY
+tTCbKGW0pXDRBhwUHRcuRzScjli8Rih5UOCiZkhefUTcRb6xIhZJuQy71tjaSy0p
+dHZRmYyBYO2YEQ8xoQJBAPrJPhMBkzmEYFtyIEqAxQ/o/A6E+E4w8i+KM7nQCK7q
+K4JXzyXVAjLfyBZWHGM2uro/fjqPggGD6QH1qXCkI4MCQQDmdKeb2TrKRh5BY1LR
+81aJGKcJ2XbcDu6wMZK4oqWbTX2KiYn9GB0woM6nSr/Y6iy1u145YzYxEV/iMwff
+DJULAkB8B2MnyzOg0pNFJqBJuH29bKCcHa8gHJzqXhNO5lAlEbMK95p/P2Wi+4Hd
+aiEIAF1BF326QJcvYKmwSmrORp85AkAlSNxRJ50OWrfMZnBgzVjDx3xG6KsFQVk2
+ol6VhqL6dFgKUORFUWBvnKSyhjJxurlPEahV6oo6+A+mPhFY8eUvAkAZQyTdupP3
+XEFQKctGz+9+gKkemDp7LBBMEMBXrGTLPhpEfcjv/7KPdnFHYmhYeBTBnuVmTVWe
+F98XJ7tIFfJq
+-----END PRIVATE KEY-----
diff --git a/grpc/interop/server1.pem b/grpc/interop/server1.pem
new file mode 100644
index 0000000..f3d43fc
--- /dev/null
+++ b/grpc/interop/server1.pem
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE-----
+MIICnDCCAgWgAwIBAgIBBzANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJBVTET
+MBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQ
+dHkgTHRkMQ8wDQYDVQQDEwZ0ZXN0Y2EwHhcNMTUxMTA0MDIyMDI0WhcNMjUxMTAx
+MDIyMDI0WjBlMQswCQYDVQQGEwJVUzERMA8GA1UECBMISWxsaW5vaXMxEDAOBgNV
+BAcTB0NoaWNhZ28xFTATBgNVBAoTDEV4YW1wbGUsIENvLjEaMBgGA1UEAxQRKi50
+ZXN0Lmdvb2dsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOHDFSco
+LCVJpYDDM4HYtIdV6Ake/sMNaaKdODjDMsux/4tDydlumN+fm+AjPEK5GHhGn1Bg
+zkWF+slf3BxhrA/8dNsnunstVA7ZBgA/5qQxMfGAq4wHNVX77fBZOgp9VlSMVfyd
+9N8YwbBYAckOeUQadTi2X1S6OgJXgQ0m3MWhAgMBAAGjazBpMAkGA1UdEwQCMAAw
+CwYDVR0PBAQDAgXgME8GA1UdEQRIMEaCECoudGVzdC5nb29nbGUuZnKCGHdhdGVy
+em9vaS50ZXN0Lmdvb2dsZS5iZYISKi50ZXN0LnlvdXR1YmUuY29thwTAqAEDMA0G
+CSqGSIb3DQEBCwUAA4GBAJFXVifQNub1LUP4JlnX5lXNlo8FxZ2a12AFQs+bzoJ6
+hM044EDjqyxUqSbVePK0ni3w1fHQB5rY9yYC5f8G7aqqTY1QOhoUk8ZTSTRpnkTh
+y4jjdvTZeLDVBlueZUTDRmy2feY5aZIU18vFDK08dTG0A87pppuv1LNIR3loveU8
+-----END CERTIFICATE-----
diff --git a/grpc/interop/tool/regenerate.sh b/grpc/interop/tool/regenerate.sh
new file mode 100755
index 0000000..066a0f9
--- /dev/null
+++ b/grpc/interop/tool/regenerate.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+mkdir -p lib/src/generated
+protoc --dart_out=grpc:lib/src/generated -Iprotos/ protos/*.proto
+rm lib/src/generated/*.pbjson.dart
+rm lib/src/generated/{empty,test}.pbenum.dart
+dartfmt -w lib/src/generated
diff --git a/grpc/lib/grpc.dart b/grpc/lib/grpc.dart
new file mode 100644
index 0000000..07bdb25
--- /dev/null
+++ b/grpc/lib/grpc.dart
@@ -0,0 +1,35 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+export 'src/auth/auth.dart';
+
+export 'src/client/call.dart';
+export 'src/client/channel.dart';
+export 'src/client/client.dart';
+export 'src/client/common.dart';
+export 'src/client/connection.dart';
+export 'src/client/method.dart';
+export 'src/client/options.dart';
+
+export 'src/server/call.dart';
+export 'src/server/handler.dart' show ServerHandler;
+export 'src/server/interceptor.dart';
+export 'src/server/server.dart';
+export 'src/server/service.dart';
+
+export 'src/shared/security.dart';
+export 'src/shared/status.dart';
+export 'src/shared/streams.dart';
+export 'src/shared/timeout.dart';
diff --git a/grpc/lib/service_api.dart b/grpc/lib/service_api.dart
new file mode 100644
index 0000000..54a455a
--- /dev/null
+++ b/grpc/lib/service_api.dart
@@ -0,0 +1,28 @@
+// Copyright (c) 2019, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/// Exports the minimum api to define server and client stubs.
+///
+/// Mainly intended to be imported by generated code.
+library service_api;
+
+export 'src/client/channel.dart' show ClientChannel;
+export 'src/client/client.dart' show Client;
+export 'src/client/common.dart' show ResponseFuture, ResponseStream;
+export 'src/client/method.dart' show ClientMethod;
+export 'src/client/options.dart' show CallOptions;
+export 'src/server/call.dart' show ServiceCall;
+export 'src/server/server.dart' show Server;
+export 'src/server/service.dart' show Service, ServiceMethod;
diff --git a/grpc/lib/src/auth/auth.dart b/grpc/lib/src/auth/auth.dart
new file mode 100644
index 0000000..27aa57d
--- /dev/null
+++ b/grpc/lib/src/auth/auth.dart
@@ -0,0 +1,167 @@
+// Copyright (c) 2018, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:googleapis_auth/auth_io.dart' as auth;
+import 'package:googleapis_auth/src/crypto/rsa_sign.dart';
+import 'package:grpc/src/shared/status.dart';
+import 'package:http/http.dart' as http;
+
+import '../client/options.dart';
+
+const _tokenExpirationThreshold = const Duration(seconds: 30);
+
+abstract class BaseAuthenticator {
+ auth.AccessToken _accessToken;
+ String _lastUri;
+
+ Future<void> authenticate(Map<String, String> metadata, String uri) async {
+ if (uri == null) {
+ throw new GrpcError.unauthenticated(
+ 'Credentials require secure transport.');
+ }
+ if (_accessToken == null || _accessToken.hasExpired || uri != _lastUri) {
+ await obtainAccessCredentials(uri);
+ _lastUri = uri;
+ }
+
+ final auth = '${_accessToken.type} ${_accessToken.data}';
+ metadata['authorization'] = auth;
+
+ if (_tokenExpiresSoon) {
+ // Token is about to expire. Extend it prematurely.
+ obtainAccessCredentials(_lastUri).catchError((_) {});
+ }
+ }
+
+ bool get _tokenExpiresSoon => _accessToken.expiry
+ .subtract(_tokenExpirationThreshold)
+ .isBefore(new DateTime.now().toUtc());
+
+ CallOptions get toCallOptions => new CallOptions(providers: [authenticate]);
+
+ Future<void> obtainAccessCredentials(String uri);
+}
+
+abstract class HttpBasedAuthenticator extends BaseAuthenticator {
+ Future<void> _call;
+
+ Future<void> obtainAccessCredentials(String uri) {
+ if (_call == null) {
+ final authClient = new http.Client();
+ _call = obtainCredentialsWithClient(authClient, uri).then((credentials) {
+ _accessToken = credentials.accessToken;
+ _call = null;
+ authClient.close();
+ });
+ }
+ return _call;
+ }
+
+ Future<auth.AccessCredentials> obtainCredentialsWithClient(
+ http.Client client, String uri);
+}
+
+class ComputeEngineAuthenticator extends HttpBasedAuthenticator {
+ Future<auth.AccessCredentials> obtainCredentialsWithClient(
+ http.Client client, String uri) =>
+ auth.obtainAccessCredentialsViaMetadataServer(client);
+}
+
+class ServiceAccountAuthenticator extends HttpBasedAuthenticator {
+ auth.ServiceAccountCredentials _serviceAccountCredentials;
+ final List<String> _scopes;
+ String _projectId;
+
+ ServiceAccountAuthenticator(String serviceAccountJson, this._scopes) {
+ final serviceAccount = jsonDecode(serviceAccountJson);
+ _serviceAccountCredentials =
+ new auth.ServiceAccountCredentials.fromJson(serviceAccount);
+ _projectId = serviceAccount['project_id'];
+ }
+
+ String get projectId => _projectId;
+
+ Future<auth.AccessCredentials> obtainCredentialsWithClient(
+ http.Client client, String uri) =>
+ auth.obtainAccessCredentialsViaServiceAccount(
+ _serviceAccountCredentials, _scopes, client);
+}
+
+class JwtServiceAccountAuthenticator extends BaseAuthenticator {
+ auth.ServiceAccountCredentials _serviceAccountCredentials;
+ String _projectId;
+ String _keyId;
+
+ JwtServiceAccountAuthenticator(String serviceAccountJson) {
+ final serviceAccount = jsonDecode(serviceAccountJson);
+ _serviceAccountCredentials =
+ new auth.ServiceAccountCredentials.fromJson(serviceAccount);
+ _projectId = serviceAccount['project_id'];
+ _keyId = serviceAccount['private_key_id'];
+ }
+
+ String get projectId => _projectId;
+
+ Future<void> obtainAccessCredentials(String uri) async {
+ _accessToken = _jwtTokenFor(_serviceAccountCredentials, _keyId, uri);
+ }
+}
+
+// TODO(jakobr): Expose in googleapis_auth.
+auth.AccessToken _jwtTokenFor(
+ auth.ServiceAccountCredentials credentials, String keyId, String uri,
+ {String user, List<String> scopes}) {
+ // Subtracting 20 seconds from current timestamp to allow for clock skew among
+ // servers.
+ final timestamp =
+ (new DateTime.now().toUtc().millisecondsSinceEpoch ~/ 1000) - 20;
+ final expiry = timestamp + 3600;
+
+ final header = <String, String>{'alg': 'RS256', 'typ': 'JWT'};
+ if (keyId != null) {
+ header['kid'] = keyId;
+ }
+
+ final claims = <String, dynamic>{
+ 'iss': credentials.email,
+ 'aud': uri,
+ 'exp': expiry,
+ 'iat': timestamp,
+ 'sub': user ?? credentials.email
+ };
+ if (scopes != null) {
+ claims['scope'] = scopes.join(' ');
+ }
+
+ final headerBase64 = _base64url(ascii.encode(jsonEncode(header)));
+ final claimsBase64 = _base64url(utf8.encode(jsonEncode(claims)));
+
+ final data = '$headerBase64.$claimsBase64';
+
+ final signer = new RS256Signer(credentials.privateRSAKey);
+ final signature = signer.sign(ascii.encode(data));
+
+ final jwt = '$data.${_base64url(signature)}';
+
+ return new auth.AccessToken('Bearer', jwt,
+ new DateTime.fromMillisecondsSinceEpoch(expiry * 1000, isUtc: true));
+}
+
+String _base64url(List<int> bytes) {
+ return base64Url.encode(bytes).replaceAll('=', '');
+}
diff --git a/grpc/lib/src/client/call.dart b/grpc/lib/src/client/call.dart
new file mode 100644
index 0000000..b1c890f
--- /dev/null
+++ b/grpc/lib/src/client/call.dart
@@ -0,0 +1,308 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:async';
+
+import 'package:http2/transport.dart';
+
+import '../shared/status.dart';
+import '../shared/streams.dart';
+
+import 'common.dart';
+import 'connection.dart';
+import 'method.dart';
+import 'options.dart';
+
+const _reservedHeaders = const [
+ 'content-type',
+ 'te',
+ 'grpc-timeout',
+ 'grpc-accept-encoding',
+ 'user-agent',
+];
+
+/// An active call to a gRPC endpoint.
+class ClientCall<Q, R> implements Response {
+ final ClientMethod<Q, R> _method;
+ final Stream<Q> _requests;
+ final CallOptions options;
+
+ final _headers = new Completer<Map<String, String>>();
+ final _trailers = new Completer<Map<String, String>>();
+ bool _hasReceivedResponses = false;
+
+ Map<String, String> _headerMetadata;
+
+ TransportStream _stream;
+ StreamController<R> _responses;
+ StreamSubscription<StreamMessage> _requestSubscription;
+ StreamSubscription<GrpcMessage> _responseSubscription;
+
+ bool isCancelled = false;
+ Timer _timeoutTimer;
+
+ ClientCall(this._method, this._requests, this.options) {
+ _responses = new StreamController(onListen: _onResponseListen);
+ if (options.timeout != null) {
+ _timeoutTimer = new Timer(options.timeout, _onTimedOut);
+ }
+ }
+
+ String get path => _method.path;
+
+ void onConnectionError(error) {
+ _terminateWithError(new GrpcError.unavailable('Error connecting: $error'));
+ }
+
+ void _terminateWithError(GrpcError error) {
+ if (!_responses.isClosed) {
+ _responses.addError(error);
+ }
+ _safeTerminate();
+ }
+
+ static Map<String, String> _sanitizeMetadata(Map<String, String> metadata) {
+ final sanitizedMetadata = <String, String>{};
+ metadata.forEach((String key, String value) {
+ final lowerCaseKey = key.trim().toLowerCase();
+ if (!lowerCaseKey.startsWith(':') &&
+ !_reservedHeaders.contains(lowerCaseKey)) {
+ sanitizedMetadata[lowerCaseKey] = value.trim();
+ }
+ });
+ return sanitizedMetadata;
+ }
+
+ void onConnectionReady(ClientConnection connection) {
+ if (isCancelled) return;
+
+ if (options.metadataProviders.isEmpty) {
+ _sendRequest(connection, _sanitizeMetadata(options.metadata));
+ } else {
+ final metadata = new Map<String, String>.from(options.metadata);
+ String audience;
+ if (connection.options.credentials.isSecure) {
+ final port = connection.port != 443 ? ':${connection.port}' : '';
+ final lastSlashPos = path.lastIndexOf('/');
+ final audiencePath =
+ lastSlashPos == -1 ? path : path.substring(0, lastSlashPos);
+ audience = 'https://${connection.authority}$port$audiencePath';
+ }
+ Future.forEach(options.metadataProviders,
+ (provider) => provider(metadata, audience))
+ .then((_) => _sendRequest(connection, _sanitizeMetadata(metadata)))
+ .catchError(_onMetadataProviderError);
+ }
+ }
+
+ void _onMetadataProviderError(error) {
+ _terminateWithError(new GrpcError.internal('Error making call: $error'));
+ }
+
+ void _sendRequest(ClientConnection connection, Map<String, String> metadata) {
+ try {
+ _stream = connection.makeRequest(path, options.timeout, metadata);
+ } catch (e) {
+ _terminateWithError(new GrpcError.unavailable('Error making call: $e'));
+ return;
+ }
+ _requestSubscription = _requests
+ .map(_method.requestSerializer)
+ .map(GrpcHttpEncoder.frame)
+ .map<StreamMessage>((bytes) => new DataStreamMessage(bytes))
+ .handleError(_onRequestError)
+ .listen(_stream.outgoingMessages.add,
+ onError: _stream.outgoingMessages.addError,
+ onDone: _stream.outgoingMessages.close,
+ cancelOnError: true);
+ // The response stream might have been listened to before _stream was ready,
+ // so try setting up the subscription here as well.
+ _onResponseListen();
+ }
+
+ void _onTimedOut() {
+ _responses.addError(new GrpcError.deadlineExceeded('Deadline exceeded'));
+ _safeTerminate();
+ }
+
+ /// Subscribe to incoming response messages, once [_stream] is available, and
+ /// the caller has subscribed to the [_responses] stream.
+ void _onResponseListen() {
+ if (_stream != null &&
+ _responses.hasListener &&
+ _responseSubscription == null) {
+ _responseSubscription = _stream.incomingMessages
+ .transform(new GrpcHttpDecoder())
+ .transform(grpcDecompressor())
+ .listen(_onResponseData,
+ onError: _onResponseError,
+ onDone: _onResponseDone,
+ cancelOnError: true);
+ if (_responses.isPaused) {
+ _responseSubscription.pause();
+ }
+ _responses.onPause = _responseSubscription.pause;
+ _responses.onResume = _responseSubscription.resume;
+ _responses.onCancel = _responseSubscription.cancel;
+ }
+ }
+
+ /// Emit an error response to the user, and tear down this call.
+ void _responseError(GrpcError error) {
+ _responses.addError(error);
+ _timeoutTimer?.cancel();
+ _requestSubscription?.cancel();
+ _responseSubscription.cancel();
+ _responses.close();
+ _stream.terminate();
+ }
+
+ /// Data handler for responses coming from the server. Handles header/trailer
+ /// metadata, and forwards response objects to [_responses].
+ void _onResponseData(GrpcMessage data) {
+ if (data is GrpcData) {
+ if (!_headers.isCompleted) {
+ _responseError(
+ new GrpcError.unimplemented('Received data before headers'));
+ return;
+ }
+ if (_trailers.isCompleted) {
+ _responseError(
+ new GrpcError.unimplemented('Received data after trailers'));
+ return;
+ }
+ _responses.add(_method.responseDeserializer(data.data));
+ _hasReceivedResponses = true;
+ } else if (data is GrpcMetadata) {
+ if (!_headers.isCompleted) {
+ // TODO(jakobr): Parse, and extract common headers.
+ _headerMetadata = data.metadata;
+ _headers.complete(_headerMetadata);
+ return;
+ }
+ if (_trailers.isCompleted) {
+ _responseError(
+ new GrpcError.unimplemented('Received multiple trailers'));
+ return;
+ }
+ final metadata = data.metadata;
+ _trailers.complete(metadata);
+ // TODO(jakobr): Parse more!
+ if (metadata.containsKey('grpc-status')) {
+ final status = int.parse(metadata['grpc-status']);
+ final message = metadata['grpc-message'];
+ if (status != 0) {
+ _responseError(new GrpcError.custom(status, message));
+ }
+ }
+ } else {
+ _responseError(new GrpcError.unimplemented('Unexpected frame received'));
+ }
+ }
+
+ /// Handler for response errors. Forward the error to the [_responses] stream,
+ /// wrapped if necessary.
+ void _onResponseError(error) {
+ if (error is GrpcError) {
+ _responseError(error);
+ return;
+ }
+ _responseError(new GrpcError.unknown(error.toString()));
+ }
+
+ /// Handles closure of the response stream. Verifies that server has sent
+ /// response messages and header/trailer metadata, as necessary.
+ void _onResponseDone() {
+ if (!_headers.isCompleted) {
+ _responseError(new GrpcError.unavailable('Did not receive anything'));
+ return;
+ }
+ if (!_trailers.isCompleted) {
+ if (_hasReceivedResponses) {
+ // Trailers are required after receiving data.
+ _responseError(new GrpcError.unavailable('Missing trailers'));
+ return;
+ }
+
+ // Only received a header frame and no data frames, so the header
+ // should contain "trailers" as well (Trailers-Only).
+ _trailers.complete(_headerMetadata);
+ final status = _headerMetadata['grpc-status'];
+ // If status code is missing, we must treat it as '0'. As in 'success'.
+ final statusCode = status != null ? int.parse(status) : 0;
+ if (statusCode != 0) {
+ final message = _headerMetadata['grpc-message'];
+ _responseError(new GrpcError.custom(statusCode, message));
+ }
+ }
+ _timeoutTimer?.cancel();
+ _responses.close();
+ _responseSubscription.cancel();
+ }
+
+ /// Error handler for the requests stream. Something went wrong while trying
+ /// to send the request to the server. Abort the request, and forward the
+ /// error to the user code on the [_responses] stream.
+ void _onRequestError(error) {
+ if (error is! GrpcError) {
+ error = new GrpcError.unknown(error.toString());
+ }
+
+ _responses.addError(error);
+ _timeoutTimer?.cancel();
+ _responses.close();
+ _requestSubscription?.cancel();
+ _responseSubscription?.cancel();
+ _stream.terminate();
+ }
+
+ Stream<R> get response => _responses.stream;
+
+ @override
+ Future<Map<String, String>> get headers => _headers.future;
+
+ @override
+ Future<Map<String, String>> get trailers => _trailers.future;
+
+ @override
+ Future<void> cancel() {
+ if (!_responses.isClosed) {
+ _responses.addError(new GrpcError.cancelled('Cancelled by client.'));
+ }
+ return _terminate();
+ }
+
+ Future<void> _terminate() async {
+ isCancelled = true;
+ _timeoutTimer?.cancel();
+ // Don't await _responses.close() here. It'll only complete once the done
+ // event has been delivered, and it's the caller of this function that is
+ // reading from responses as well, so we might end up deadlocked.
+ _responses.close();
+ _stream?.terminate();
+ final futures = <Future>[];
+ if (_requestSubscription != null) {
+ futures.add(_requestSubscription.cancel());
+ }
+ if (_responseSubscription != null) {
+ futures.add(_responseSubscription.cancel());
+ }
+ await Future.wait(futures);
+ }
+
+ Future<void> _safeTerminate() {
+ return _terminate().catchError((_) {});
+ }
+}
diff --git a/grpc/lib/src/client/channel.dart b/grpc/lib/src/client/channel.dart
new file mode 100644
index 0000000..68bd22f
--- /dev/null
+++ b/grpc/lib/src/client/channel.dart
@@ -0,0 +1,80 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:async';
+
+import '../shared/status.dart';
+
+import 'call.dart';
+import 'connection.dart';
+import 'method.dart';
+import 'options.dart';
+
+/// A channel to a virtual RPC endpoint.
+///
+/// For each RPC, the channel picks a [ClientConnection] to dispatch the call.
+/// RPCs on the same channel may be sent to different connections, depending on
+/// load balancing settings.
+class ClientChannel {
+ final String host;
+ final int port;
+ final ChannelOptions options;
+
+ // TODO(jakobr): Multiple connections, load balancing.
+ ClientConnection _connection;
+
+ bool _isShutdown = false;
+
+ ClientChannel(this.host,
+ {this.port = 443, this.options = const ChannelOptions()});
+
+ /// Shuts down this channel.
+ ///
+ /// No further RPCs can be made on this channel. RPCs already in progress will
+ /// be allowed to complete.
+ Future<void> shutdown() async {
+ if (_isShutdown) return;
+ _isShutdown = true;
+ if (_connection != null) await _connection.shutdown();
+ }
+
+ /// Terminates this channel.
+ ///
+ /// RPCs already in progress will be terminated. No further RPCs can be made
+ /// on this channel.
+ Future<void> terminate() async {
+ _isShutdown = true;
+ if (_connection != null) await _connection.terminate();
+ }
+
+ /// Returns a connection to this [Channel]'s RPC endpoint.
+ ///
+ /// The connection may be shared between multiple RPCs.
+ Future<ClientConnection> getConnection() async {
+ if (_isShutdown) throw new GrpcError.unavailable('Channel shutting down.');
+ return _connection ??= new ClientConnection(host, port, options);
+ }
+
+ /// Initiates a new RPC on this connection.
+ ClientCall<Q, R> createCall<Q, R>(
+ ClientMethod<Q, R> method, Stream<Q> requests, CallOptions options) {
+ final call = new ClientCall(method, requests, options);
+ getConnection().then((connection) {
+ if (call.isCancelled) return;
+ connection.dispatchCall(call);
+ }, onError: call.onConnectionError);
+ return call;
+ }
+}
diff --git a/grpc/lib/src/client/client.dart b/grpc/lib/src/client/client.dart
new file mode 100644
index 0000000..cce6d1c
--- /dev/null
+++ b/grpc/lib/src/client/client.dart
@@ -0,0 +1,36 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:async';
+
+import 'call.dart';
+import 'channel.dart';
+import 'method.dart';
+import 'options.dart';
+
+/// Base class for client stubs.
+class Client {
+ final ClientChannel _channel;
+ final CallOptions _options;
+
+ Client(this._channel, {CallOptions options})
+ : _options = options ?? new CallOptions();
+
+ ClientCall<Q, R> $createCall<Q, R>(
+ ClientMethod<Q, R> method, Stream<Q> requests,
+ {CallOptions options}) {
+ return _channel.createCall(method, requests, _options.mergedWith(options));
+ }
+}
diff --git a/grpc/lib/src/client/common.dart b/grpc/lib/src/client/common.dart
new file mode 100644
index 0000000..87189a7
--- /dev/null
+++ b/grpc/lib/src/client/common.dart
@@ -0,0 +1,87 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:async';
+
+import 'package:async/async.dart';
+
+import '../shared/status.dart';
+import 'call.dart';
+
+/// A gRPC response.
+abstract class Response {
+ /// Header metadata returned from the server.
+ ///
+ /// The [headers] future will complete before any response objects become
+ /// available. If [cancel] is called before the headers are available, the
+ /// returned future will complete with an error.
+ Future<Map<String, String>> get headers;
+
+ /// Trailer metadata returned from the server.
+ ///
+ /// The [trailers] future will complete after all responses have been received
+ /// from the server. If [cancel] is called before the trailers are available,
+ /// the returned future will complete with an error.
+ Future<Map<String, String>> get trailers;
+
+ /// Cancel this gRPC call. Any remaining request objects will not be sent, and
+ /// no further responses will be received.
+ Future<void> cancel();
+}
+
+/// A gRPC response producing a single value.
+class ResponseFuture<R> extends DelegatingFuture<R>
+ with _ResponseMixin<dynamic, R> {
+ final ClientCall<dynamic, R> _call;
+
+ static R _ensureOnlyOneResponse<R>(R previous, R element) {
+ if (previous != null) {
+ throw new GrpcError.unimplemented('More than one response received');
+ }
+ return element;
+ }
+
+ static R _ensureOneResponse<R>(R value) {
+ if (value == null)
+ throw new GrpcError.unimplemented('No responses received');
+ return value;
+ }
+
+ ResponseFuture(this._call)
+ : super(_call.response
+ .fold(null, _ensureOnlyOneResponse)
+ .then(_ensureOneResponse));
+}
+
+/// A gRPC response producing a stream of values.
+class ResponseStream<R> extends DelegatingStream<R>
+ with _ResponseMixin<dynamic, R> {
+ final ClientCall<dynamic, R> _call;
+
+ ResponseStream(this._call) : super(_call.response);
+}
+
+abstract class _ResponseMixin<Q, R> implements Response {
+ ClientCall<Q, R> get _call;
+
+ @override
+ Future<Map<String, String>> get headers => _call.headers;
+
+ @override
+ Future<Map<String, String>> get trailers => _call.trailers;
+
+ @override
+ Future<void> cancel() => _call.cancel();
+}
diff --git a/grpc/lib/src/client/connection.dart b/grpc/lib/src/client/connection.dart
new file mode 100644
index 0000000..371b3c0
--- /dev/null
+++ b/grpc/lib/src/client/connection.dart
@@ -0,0 +1,286 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:http2/transport.dart';
+import 'package:meta/meta.dart';
+
+import '../shared/timeout.dart';
+
+import 'call.dart';
+import 'options.dart';
+
+enum ConnectionState {
+ /// Actively trying to connect.
+ connecting,
+
+ /// Connection successfully established.
+ ready,
+
+ /// Some transient failure occurred, waiting to re-connect.
+ transientFailure,
+
+ /// Not currently connected, and no pending RPCs.
+ idle,
+
+ /// Shutting down, no further RPCs allowed.
+ shutdown
+}
+
+/// A connection to a single RPC endpoint.
+///
+/// RPCs made on a connection are always sent to the same endpoint.
+class ClientConnection {
+ static final _methodPost = new Header.ascii(':method', 'POST');
+ static final _schemeHttp = new Header.ascii(':scheme', 'http');
+ static final _schemeHttps = new Header.ascii(':scheme', 'https');
+ static final _contentTypeGrpc =
+ new Header.ascii('content-type', 'application/grpc');
+ static final _teTrailers = new Header.ascii('te', 'trailers');
+ static final _grpcAcceptEncoding =
+ new Header.ascii('grpc-accept-encoding', 'identity');
+
+ final String host;
+ final int port;
+ final ChannelOptions options;
+
+ ConnectionState _state = ConnectionState.idle;
+ void Function(ClientConnection connection) onStateChanged;
+ final _pendingCalls = <ClientCall>[];
+
+ ClientTransportConnection _transport;
+
+ /// Used for idle and reconnect timeout, depending on [_state].
+ Timer _timer;
+ Duration _currentReconnectDelay;
+
+ ClientConnection(this.host, this.port, this.options);
+
+ ConnectionState get state => _state;
+
+ static List<Header> createCallHeaders(bool useTls, String authority,
+ String path, Duration timeout, Map<String, String> metadata,
+ {String userAgent}) {
+ final headers = [
+ _methodPost,
+ useTls ? _schemeHttps : _schemeHttp,
+ new Header(ascii.encode(':path'), utf8.encode(path)),
+ new Header(ascii.encode(':authority'), utf8.encode(authority)),
+ ];
+ if (timeout != null) {
+ headers.add(new Header.ascii('grpc-timeout', toTimeoutString(timeout)));
+ }
+ headers.addAll([
+ _contentTypeGrpc,
+ _teTrailers,
+ _grpcAcceptEncoding,
+ new Header.ascii('user-agent', userAgent ?? defaultUserAgent),
+ ]);
+ metadata?.forEach((key, value) {
+ headers.add(new Header(ascii.encode(key), utf8.encode(value)));
+ });
+ return headers;
+ }
+
+ String get authority => options.credentials.authority ?? host;
+
+ @visibleForTesting
+ Future<ClientTransportConnection> connectTransport() async {
+ final securityContext = options.credentials.securityContext;
+
+ var socket = await Socket.connect(host, port);
+ if (_state == ConnectionState.shutdown) {
+ socket.destroy();
+ throw 'Shutting down';
+ }
+ if (securityContext != null) {
+ socket = await SecureSocket.secure(socket,
+ host: authority,
+ context: securityContext,
+ onBadCertificate: _validateBadCertificate);
+ if (_state == ConnectionState.shutdown) {
+ socket.destroy();
+ throw 'Shutting down';
+ }
+ }
+ socket.done.then(_handleSocketClosed);
+ return new ClientTransportConnection.viaSocket(socket);
+ }
+
+ bool _validateBadCertificate(X509Certificate certificate) {
+ final validator = options.credentials.onBadCertificate;
+ if (validator == null) return false;
+ return validator(certificate, authority);
+ }
+
+ void _connect() {
+ if (_state != ConnectionState.idle &&
+ _state != ConnectionState.transientFailure) {
+ return;
+ }
+ _setState(ConnectionState.connecting);
+ connectTransport().then((transport) {
+ _currentReconnectDelay = null;
+ _transport = transport;
+ _transport.onActiveStateChanged = _handleActiveStateChanged;
+ _setState(ConnectionState.ready);
+ _pendingCalls.forEach(_startCall);
+ _pendingCalls.clear();
+ }).catchError(_handleConnectionFailure);
+ }
+
+ void dispatchCall(ClientCall call) {
+ switch (_state) {
+ case ConnectionState.ready:
+ _startCall(call);
+ break;
+ case ConnectionState.shutdown:
+ _shutdownCall(call);
+ break;
+ default:
+ _pendingCalls.add(call);
+ if (_state == ConnectionState.idle) {
+ _connect();
+ }
+ }
+ }
+
+ ClientTransportStream makeRequest(
+ String path, Duration timeout, Map<String, String> metadata) {
+ final headers = createCallHeaders(
+ options.credentials.isSecure, authority, path, timeout, metadata,
+ userAgent: options.userAgent);
+ return _transport.makeRequest(headers);
+ }
+
+ void _startCall(ClientCall call) {
+ if (call.isCancelled) return;
+ call.onConnectionReady(this);
+ }
+
+ void _failCall(ClientCall call, dynamic error) {
+ if (call.isCancelled) return;
+ call.onConnectionError(error);
+ }
+
+ void _shutdownCall(ClientCall call) {
+ _failCall(call, 'Connection shutting down.');
+ }
+
+ /// Shuts down this connection.
+ ///
+ /// No further calls may be made on this connection, but existing calls
+ /// are allowed to finish.
+ Future<void> shutdown() async {
+ if (_state == ConnectionState.shutdown) return null;
+ _setShutdownState();
+ await _transport?.finish();
+ }
+
+ /// Terminates this connection.
+ ///
+ /// All open calls are terminated immediately, and no further calls may be
+ /// made on this connection.
+ Future<void> terminate() async {
+ _setShutdownState();
+ await _transport?.terminate();
+ }
+
+ void _setShutdownState() {
+ _setState(ConnectionState.shutdown);
+ _cancelTimer();
+ _pendingCalls.forEach(_shutdownCall);
+ _pendingCalls.clear();
+ }
+
+ void _setState(ConnectionState state) {
+ _state = state;
+ if (onStateChanged != null) {
+ onStateChanged(this);
+ }
+ }
+
+ void _handleIdleTimeout() {
+ if (_timer == null || _state != ConnectionState.ready) return;
+ _cancelTimer();
+ _transport?.finish()?.catchError((_) => {}); // TODO(jakobr): Log error.
+ _transport = null;
+ _setState(ConnectionState.idle);
+ }
+
+ void _cancelTimer() {
+ _timer?.cancel();
+ _timer = null;
+ }
+
+ void _handleActiveStateChanged(bool isActive) {
+ if (isActive) {
+ _cancelTimer();
+ } else {
+ if (options.idleTimeout != null) {
+ _timer ??= new Timer(options.idleTimeout, _handleIdleTimeout);
+ }
+ }
+ }
+
+ bool _hasPendingCalls() {
+ // Get rid of pending calls that have timed out.
+ _pendingCalls.removeWhere((call) => call.isCancelled);
+ return _pendingCalls.isNotEmpty;
+ }
+
+ void _handleConnectionFailure(error) {
+ _transport = null;
+ if (_state == ConnectionState.shutdown || _state == ConnectionState.idle) {
+ return;
+ }
+ // TODO(jakobr): Log error.
+ _cancelTimer();
+ _pendingCalls.forEach((call) => _failCall(call, error));
+ _pendingCalls.clear();
+ _setState(ConnectionState.idle);
+ }
+
+ void _handleReconnect() {
+ if (_timer == null || _state != ConnectionState.transientFailure) return;
+ _cancelTimer();
+ _connect();
+ }
+
+ void _handleSocketClosed(_) {
+ _cancelTimer();
+ _transport = null;
+
+ if (_state == ConnectionState.idle && _state == ConnectionState.shutdown) {
+ // All good.
+ return;
+ }
+
+ // We were not planning to close the socket.
+ if (!_hasPendingCalls()) {
+ // No pending calls. Just hop to idle, and wait for a new RPC.
+ _setState(ConnectionState.idle);
+ return;
+ }
+
+ // We have pending RPCs. Reconnect after backoff delay.
+ _setState(ConnectionState.transientFailure);
+ _currentReconnectDelay = options.backoffStrategy(_currentReconnectDelay);
+ _timer = new Timer(_currentReconnectDelay, _handleReconnect);
+ }
+}
diff --git a/grpc/lib/src/client/method.dart b/grpc/lib/src/client/method.dart
new file mode 100644
index 0000000..dca28eb
--- /dev/null
+++ b/grpc/lib/src/client/method.dart
@@ -0,0 +1,23 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/// Description of a gRPC method.
+class ClientMethod<Q, R> {
+ final String path;
+ final List<int> Function(Q value) requestSerializer;
+ final R Function(List<int> value) responseDeserializer;
+
+ ClientMethod(this.path, this.requestSerializer, this.responseDeserializer);
+}
diff --git a/grpc/lib/src/client/options.dart b/grpc/lib/src/client/options.dart
new file mode 100644
index 0000000..90e1d94
--- /dev/null
+++ b/grpc/lib/src/client/options.dart
@@ -0,0 +1,159 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:async';
+import 'dart:io';
+
+import 'dart:math';
+
+import '../shared/security.dart';
+
+const defaultIdleTimeout = const Duration(minutes: 5);
+const defaultUserAgent = 'dart-grpc/1.0.3';
+
+typedef Duration BackoffStrategy(Duration lastBackoff);
+
+// Backoff algorithm from https://github.com/grpc/grpc/blob/master/doc/connection-backoff.md
+const _minConnectTimeout = const Duration(seconds: 20);
+const _initialBackoff = const Duration(seconds: 1);
+const _maxBackoff = const Duration(seconds: 120);
+const _multiplier = 1.6;
+const _jitter = 0.2;
+final _random = new Random();
+
+Duration defaultBackoffStrategy(Duration lastBackoff) {
+ if (lastBackoff == null) return _initialBackoff;
+ final jitter = _random.nextDouble() * 2 * _jitter - _jitter;
+ final nextBackoff = lastBackoff * (_multiplier + jitter);
+ return nextBackoff < _maxBackoff ? nextBackoff : _maxBackoff;
+}
+
+/// Handler for checking certificates that fail validation. If this handler
+/// returns `true`, the bad certificate is allowed, and the TLS handshake can
+/// continue. If the handler returns `false`, the TLS handshake fails, and the
+/// connection is aborted.
+typedef bool BadCertificateHandler(X509Certificate certificate, String host);
+
+/// Bad certificate handler that disables all certificate checks.
+/// DO NOT USE IN PRODUCTION!
+/// Can be used during development and testing to accept self-signed
+/// certificates, etc.
+bool allowBadCertificates(X509Certificate certificate, String host) => true;
+
+/// Options controlling TLS security settings on a [ClientChannel].
+class ChannelCredentials {
+ final bool isSecure;
+ final List<int> _certificateBytes;
+ final String _certificatePassword;
+ final String authority;
+ final BadCertificateHandler onBadCertificate;
+
+ const ChannelCredentials._(this.isSecure, this._certificateBytes,
+ this._certificatePassword, this.authority, this.onBadCertificate);
+
+ /// Disable TLS. RPCs are sent in clear text.
+ const ChannelCredentials.insecure({String authority})
+ : this._(false, null, null, authority, null);
+
+ /// Enable TLS and optionally specify the [certificates] to trust. If
+ /// [certificates] is not provided, the default trust store is used.
+ const ChannelCredentials.secure(
+ {List<int> certificates,
+ String password,
+ String authority,
+ BadCertificateHandler onBadCertificate})
+ : this._(true, certificates, password, authority, onBadCertificate);
+
+ SecurityContext get securityContext {
+ if (!isSecure) return null;
+ if (_certificateBytes != null) {
+ return createSecurityContext(false)
+ ..setTrustedCertificatesBytes(_certificateBytes,
+ password: _certificatePassword);
+ }
+ final context = new SecurityContext(withTrustedRoots: true);
+ if (SecurityContext.alpnSupported) {
+ context.setAlpnProtocols(supportedAlpnProtocols, false);
+ }
+ return context;
+ }
+}
+
+/// Options controlling how connections are made on a [ClientChannel].
+class ChannelOptions {
+ final ChannelCredentials credentials;
+ final Duration idleTimeout;
+ final BackoffStrategy backoffStrategy;
+ final String userAgent;
+
+ const ChannelOptions({
+ ChannelCredentials credentials,
+ Duration idleTimeout,
+ String userAgent,
+ BackoffStrategy backoffStrategy,
+ }) : this.credentials = credentials ?? const ChannelCredentials.secure(),
+ this.idleTimeout = idleTimeout ?? defaultIdleTimeout,
+ this.userAgent = userAgent ?? defaultUserAgent,
+ this.backoffStrategy = backoffStrategy ?? defaultBackoffStrategy;
+}
+
+/// Provides per-RPC metadata.
+///
+/// Metadata providers will be invoked for every RPC, and can add their own
+/// metadata to the RPC. If the function returns a [Future], the RPC will await
+/// completion of the returned [Future] before transmitting the request.
+///
+/// The metadata provider is given the current [metadata] map (possibly modified
+/// by previous metadata providers) and the [uri] that is being called, and is
+/// expected to modify the map before returning or before completing the
+/// returned [Future].
+typedef FutureOr<void> MetadataProvider(
+ Map<String, String> metadata, String uri);
+
+/// Runtime options for an RPC.
+class CallOptions {
+ final Map<String, String> metadata;
+ final Duration timeout;
+ final List<MetadataProvider> metadataProviders;
+
+ CallOptions._(this.metadata, this.timeout, this.metadataProviders);
+
+ /// Creates a [CallOptions] object.
+ ///
+ /// [CallOptions] can specify static [metadata], set the [timeout], and
+ /// configure per-RPC metadata [providers]. The metadata [providers] are
+ /// invoked in order for every RPC, and can modify the outgoing metadata
+ /// (including metadata provided by previous providers).
+ factory CallOptions(
+ {Map<String, String> metadata,
+ Duration timeout,
+ List<MetadataProvider> providers}) {
+ return new CallOptions._(new Map.unmodifiable(metadata ?? {}), timeout,
+ new List.unmodifiable(providers ?? []));
+ }
+
+ factory CallOptions.from(Iterable<CallOptions> options) =>
+ options.fold(new CallOptions(), (p, o) => p.mergedWith(o));
+
+ CallOptions mergedWith(CallOptions other) {
+ if (other == null) return this;
+ final mergedMetadata = new Map.from(metadata)..addAll(other.metadata);
+ final mergedTimeout = other.timeout ?? timeout;
+ final mergedProviders = new List.from(metadataProviders)
+ ..addAll(other.metadataProviders);
+ return new CallOptions._(new Map.unmodifiable(mergedMetadata),
+ mergedTimeout, new List.unmodifiable(mergedProviders));
+ }
+}
diff --git a/grpc/lib/src/server/call.dart b/grpc/lib/src/server/call.dart
new file mode 100644
index 0000000..656dc48
--- /dev/null
+++ b/grpc/lib/src/server/call.dart
@@ -0,0 +1,54 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/// Server-side context for a gRPC call.
+///
+/// Gives the method handler access to custom metadata from the client, and
+/// ability to set custom metadata on the header/trailer sent to the client.
+abstract class ServiceCall {
+ /// Custom metadata from the client.
+ Map<String, String> get clientMetadata;
+
+ /// Custom metadata to be sent to the client. Will be [null] once the headers
+ /// have been sent, either when [sendHeaders] is called, or when the first
+ /// response message is sent.
+ Map<String, String> get headers;
+
+ /// Custom metadata to be sent to the client after all response messages.
+ Map<String, String> get trailers;
+
+ /// Deadline for this call. If the call is still active after this time, then
+ /// the client or server may cancel it.
+ DateTime get deadline;
+
+ /// Returns [true] if the [deadline] has been exceeded.
+ bool get isTimedOut;
+
+ /// Returns [true] if the client has canceled this call.
+ bool get isCanceled;
+
+ /// Send response headers. This is done automatically before sending the first
+ /// response message, but can be done manually before the first response is
+ /// ready, if necessary.
+ void sendHeaders();
+
+ /// Send response trailers. A trailer indicating success ([status] == 0) will
+ /// be sent automatically when all responses are sent. This method can be used
+ /// to send a different status code, if needed.
+ ///
+ /// The call will be closed after calling this method, and no further
+ /// responses can be sent.
+ void sendTrailers({int status, String message});
+}
diff --git a/grpc/lib/src/server/handler.dart b/grpc/lib/src/server/handler.dart
new file mode 100644
index 0000000..74754e4
--- /dev/null
+++ b/grpc/lib/src/server/handler.dart
@@ -0,0 +1,364 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:async';
+import 'dart:convert';
+
+import 'package:http2/transport.dart';
+
+import '../shared/status.dart';
+import '../shared/streams.dart';
+import '../shared/timeout.dart';
+
+import 'call.dart';
+import 'interceptor.dart';
+import 'service.dart';
+
+/// Handles an incoming gRPC call.
+class ServerHandler_ extends ServiceCall {
+ final ServerTransportStream _stream;
+ final Service Function(String service) _serviceLookup;
+ final List<Interceptor> _interceptors;
+
+ StreamSubscription<GrpcMessage> _incomingSubscription;
+
+ Service _service;
+ ServiceMethod _descriptor;
+
+ Map<String, String> _clientMetadata;
+
+ StreamController _requests;
+ bool _hasReceivedRequest = false;
+
+ Stream _responses;
+ StreamSubscription _responseSubscription;
+ bool _headersSent = false;
+
+ Map<String, String> _customHeaders = {};
+ Map<String, String> _customTrailers = {};
+
+ DateTime _deadline;
+ bool _isCanceled = false;
+ bool _isTimedOut = false;
+ Timer _timeoutTimer;
+
+ ServerHandler_(this._serviceLookup, this._stream, this._interceptors);
+
+ DateTime get deadline => _deadline;
+
+ bool get isCanceled => _isCanceled;
+
+ bool get isTimedOut => _isTimedOut;
+
+ Map<String, String> get clientMetadata => _clientMetadata;
+
+ Map<String, String> get headers => _customHeaders;
+
+ Map<String, String> get trailers => _customTrailers;
+
+ void handle() {
+ _stream.onTerminated = (_) => cancel();
+
+ _incomingSubscription = _stream.incomingMessages
+ .transform(new GrpcHttpDecoder())
+ .transform(grpcDecompressor())
+ .listen(_onDataIdle,
+ onError: _onError, onDone: _onDoneError, cancelOnError: true);
+ }
+
+ /// Cancel response subscription, if active. If the stream exits with an
+ /// error, just ignore it. The client is long gone, so it doesn't care.
+ /// We need the catchError() handler here, since otherwise the error would
+ /// be an unhandled exception.
+ void _cancelResponseSubscription() {
+ _responseSubscription?.cancel()?.catchError((_) {});
+ }
+
+ // -- Idle state, incoming data --
+
+ void _onDataIdle(GrpcMessage message) async {
+ if (message is! GrpcMetadata) {
+ _sendError(new GrpcError.unimplemented('Expected header frame'));
+ _sinkIncoming();
+ return;
+ }
+ _incomingSubscription.pause();
+
+ final headerMessage = message
+ as GrpcMetadata; // TODO(jakobr): Cast should not be necessary here.
+ _clientMetadata = headerMessage.metadata;
+ final path = _clientMetadata[':path'];
+ final pathSegments = path.split('/');
+ if (pathSegments.length < 3) {
+ _sendError(new GrpcError.unimplemented('Invalid path'));
+ _sinkIncoming();
+ return;
+ }
+ final serviceName = pathSegments[1];
+ final methodName = pathSegments[2];
+
+ _service = _serviceLookup(serviceName);
+ _descriptor = _service?.$lookupMethod(methodName);
+ if (_descriptor == null) {
+ _sendError(new GrpcError.unimplemented('Path $path not found'));
+ _sinkIncoming();
+ return;
+ }
+
+ final error = await _applyInterceptors();
+ if (error != null) {
+ _sendError(error);
+ _sinkIncoming();
+ return;
+ }
+
+ _startStreamingRequest();
+ }
+
+ Future<GrpcError> _applyInterceptors() async {
+ try {
+ for (final interceptor in _interceptors) {
+ final error = await interceptor(this, this._descriptor);
+ if (error != null) {
+ return error;
+ }
+ }
+ } catch (error) {
+ final grpcError = new GrpcError.internal(error.toString());
+ return grpcError;
+ }
+ return null;
+ }
+
+ void _startStreamingRequest() {
+ _requests = _descriptor.createRequestStream(_incomingSubscription);
+ _incomingSubscription.onData(_onDataActive);
+
+ _service.$onMetadata(this);
+ _responses = _descriptor.handle(this, _requests.stream);
+
+ _responseSubscription = _responses.listen(_onResponse,
+ onError: _onResponseError,
+ onDone: _onResponseDone,
+ cancelOnError: true);
+ _incomingSubscription.onData(_onDataActive);
+ _incomingSubscription.onDone(_onDoneExpected);
+
+ final timeout = fromTimeoutString(_clientMetadata['grpc-timeout']);
+ if (timeout != null) {
+ _deadline = new DateTime.now().add(timeout);
+ _timeoutTimer = new Timer(timeout, _onTimedOut);
+ }
+ }
+
+ void _onTimedOut() {
+ if (_isCanceled) return;
+ _isTimedOut = true;
+ _isCanceled = true;
+ final error = new GrpcError.deadlineExceeded('Deadline exceeded');
+ _sendError(error);
+ if (!_requests.isClosed) {
+ _requests
+ ..addError(error)
+ ..close();
+ }
+ }
+
+ // -- Active state, incoming data --
+
+ void _onDataActive(GrpcMessage message) {
+ if (message is! GrpcData) {
+ final error = new GrpcError.unimplemented('Expected request');
+ _sendError(error);
+ _requests
+ ..addError(error)
+ ..close();
+ return;
+ }
+
+ if (_hasReceivedRequest && !_descriptor.streamingRequest) {
+ final error = new GrpcError.unimplemented('Too many requests');
+ _sendError(error);
+ _requests
+ ..addError(error)
+ ..close();
+ return;
+ }
+
+ // TODO(jakobr): Cast should not be necessary here.
+ final data = message as GrpcData;
+ var request;
+ try {
+ request = _descriptor.deserialize(data.data);
+ } catch (error) {
+ final grpcError =
+ new GrpcError.internal('Error deserializing request: $error');
+ _sendError(grpcError);
+ _requests
+ ..addError(grpcError)
+ ..close();
+ return;
+ }
+ _requests.add(request);
+ _hasReceivedRequest = true;
+ }
+
+ // -- Active state, outgoing response data --
+
+ void _onResponse(response) {
+ try {
+ final bytes = _descriptor.serialize(response);
+ if (!_headersSent) {
+ sendHeaders();
+ }
+ _stream.sendData(GrpcHttpEncoder.frame(bytes));
+ } catch (error) {
+ final grpcError =
+ new GrpcError.internal('Error sending response: $error');
+ if (!_requests.isClosed) {
+ // If we can, alert the handler that things are going wrong.
+ _requests
+ ..addError(grpcError)
+ ..close();
+ }
+ _sendError(grpcError);
+ _cancelResponseSubscription();
+ }
+ }
+
+ void _onResponseDone() {
+ sendTrailers();
+ }
+
+ void _onResponseError(error) {
+ if (error is GrpcError) {
+ _sendError(error);
+ } else {
+ _sendError(new GrpcError.unknown(error.toString()));
+ }
+ }
+
+ void sendHeaders() {
+ if (_headersSent) throw new GrpcError.internal('Headers already sent');
+
+ _customHeaders..remove(':status')..remove('content-type');
+
+ // TODO(jakobr): Should come from package:http2?
+ final outgoingHeadersMap = <String, String>{
+ ':status': '200',
+ 'content-type': 'application/grpc'
+ };
+
+ outgoingHeadersMap.addAll(_customHeaders);
+ _customHeaders = null;
+
+ final outgoingHeaders = <Header>[];
+ outgoingHeadersMap.forEach((key, value) =>
+ outgoingHeaders.add(new Header(ascii.encode(key), utf8.encode(value))));
+ _stream.sendHeaders(outgoingHeaders);
+ _headersSent = true;
+ }
+
+ void sendTrailers({int status = 0, String message}) {
+ _timeoutTimer?.cancel();
+
+ final outgoingTrailersMap = <String, String>{};
+ if (!_headersSent) {
+ // TODO(jakobr): Should come from package:http2?
+ outgoingTrailersMap[':status'] = '200';
+ outgoingTrailersMap['content-type'] = 'application/grpc';
+
+ _customHeaders..remove(':status')..remove('content-type');
+ outgoingTrailersMap.addAll(_customHeaders);
+ _customHeaders = null;
+ }
+ _customTrailers..remove(':status')..remove('content-type');
+ outgoingTrailersMap.addAll(_customTrailers);
+ _customTrailers = null;
+ outgoingTrailersMap['grpc-status'] = status.toString();
+ if (message != null) {
+ outgoingTrailersMap['grpc-message'] = message;
+ }
+
+ final outgoingTrailers = <Header>[];
+ outgoingTrailersMap.forEach((key, value) => outgoingTrailers
+ .add(new Header(ascii.encode(key), utf8.encode(value))));
+ _stream.sendHeaders(outgoingTrailers, endStream: true);
+ // We're done!
+ _cancelResponseSubscription();
+ _sinkIncoming();
+ }
+
+ // -- All states, incoming error / stream closed --
+
+ void _onError(error) {
+ // Exception from the incoming stream. Most likely a cancel request from the
+ // client, so we treat it as such.
+ _timeoutTimer?.cancel();
+ _isCanceled = true;
+ if (_requests != null && !_requests.isClosed) {
+ _requests.addError(new GrpcError.cancelled('Cancelled'));
+ }
+ _cancelResponseSubscription();
+ _incomingSubscription.cancel();
+ _stream.terminate();
+ }
+
+ void _onDoneError() {
+ _sendError(new GrpcError.unavailable('Request stream closed unexpectedly'));
+ _onDone();
+ }
+
+ void _onDoneExpected() {
+ if (!(_hasReceivedRequest || _descriptor.streamingRequest)) {
+ final error = new GrpcError.unimplemented('No request received');
+ _sendError(error);
+ _requests.addError(error);
+ }
+ _onDone();
+ }
+
+ void _onDone() {
+ _requests?.close();
+ _incomingSubscription.cancel();
+ }
+
+ /// Sink incoming requests. This is used when an error has already been
+ /// reported, but we still need to consume the request stream from the client.
+ void _sinkIncoming() {
+ _incomingSubscription
+ ..onData((_) {})
+ ..onDone(_onDone);
+ }
+
+ void _sendError(GrpcError error) {
+ sendTrailers(status: error.code, message: error.message);
+ }
+
+ void cancel() {
+ _isCanceled = true;
+ _timeoutTimer?.cancel();
+ _cancelResponseSubscription();
+ }
+}
+
+@Deprecated(
+ 'This is an internal class, and will not be part of the public interface in next major version.')
+// TODO(sigurdm): Remove this class from grpc.dart exports.
+class ServerHandler extends ServerHandler_ {
+ ServerHandler(Service Function(String service) serviceLookup, stream,
+ [List<Interceptor> interceptors = const <Interceptor>[]])
+ : super(serviceLookup, stream, interceptors);
+}
diff --git a/grpc/lib/src/server/interceptor.dart b/grpc/lib/src/server/interceptor.dart
new file mode 100644
index 0000000..65426a6
--- /dev/null
+++ b/grpc/lib/src/server/interceptor.dart
@@ -0,0 +1,14 @@
+import 'dart:async';
+
+import '../shared/status.dart';
+import 'call.dart';
+import 'service.dart';
+
+/// A gRPC Interceptor.
+///
+/// An interceptor is called before the corresponding [ServiceMethod] invocation.
+/// If the interceptor returns a [GrpcError], the error will be returned as a response and [ServiceMethod] wouldn't be called.
+/// If the interceptor throws [Exception], [GrpcError.internal] with exception.toString() will be returned.
+/// If the interceptor returns null, the corresponding [ServiceMethod] of [Service] will be called.
+typedef Interceptor = FutureOr<GrpcError> Function(
+ ServiceCall call, ServiceMethod method);
diff --git a/grpc/lib/src/server/server.dart b/grpc/lib/src/server/server.dart
new file mode 100644
index 0000000..d549f63
--- /dev/null
+++ b/grpc/lib/src/server/server.dart
@@ -0,0 +1,148 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:async';
+import 'dart:io';
+
+import 'package:http2/transport.dart';
+import 'package:meta/meta.dart';
+
+import '../shared/security.dart';
+
+import 'handler.dart';
+import 'interceptor.dart';
+import 'service.dart';
+
+class ServerTlsCredentials {
+ final List<int> certificate;
+ final String certificatePassword;
+ final List<int> privateKey;
+ final String privateKeyPassword;
+
+ /// TLS credentials for a [Server].
+ ///
+ /// If the [certificate] or [privateKey] is encrypted, the password must also
+ /// be provided.
+ ServerTlsCredentials(
+ {this.certificate,
+ this.certificatePassword,
+ this.privateKey,
+ this.privateKeyPassword});
+
+ SecurityContext get securityContext {
+ final context = createSecurityContext(true);
+ if (privateKey != null) {
+ context.usePrivateKeyBytes(privateKey, password: privateKeyPassword);
+ }
+ if (certificate != null) {
+ context.useCertificateChainBytes(certificate,
+ password: certificatePassword);
+ }
+ return context;
+ }
+}
+
+/// A gRPC server.
+///
+/// Listens for incoming RPCs, dispatching them to the right [Service] handler.
+class Server {
+ final Map<String, Service> _services = {};
+ final List<Interceptor> _interceptors;
+
+ ServerSocket _insecureServer;
+ SecureServerSocket _secureServer;
+ final _connections = <ServerTransportConnection>[];
+
+ /// Create a server for the given [services].
+ Server(List<Service> services,
+ [List<Interceptor> interceptors = const <Interceptor>[]])
+ : _interceptors = interceptors {
+ for (final service in services) {
+ _services[service.$name] = service;
+ }
+ }
+
+ /// The port that the server is listening on, or `null` if the server is not
+ /// active.
+ int get port {
+ if (_secureServer != null) return _secureServer.port;
+ if (_insecureServer != null) return _insecureServer.port;
+ return null;
+ }
+
+ Service lookupService(String service) => _services[service];
+
+ Future<void> serve(
+ {dynamic address, int port, ServerTlsCredentials security}) async {
+ // TODO(dart-lang/grpc-dart#9): Handle HTTP/1.1 upgrade to h2c, if allowed.
+ Stream<Socket> server;
+ if (security != null) {
+ _secureServer = await SecureServerSocket.bind(
+ address ?? InternetAddress.anyIPv4,
+ port ?? 443,
+ security.securityContext);
+ server = _secureServer;
+ } else {
+ _insecureServer = await ServerSocket.bind(
+ address ?? InternetAddress.anyIPv4, port ?? 80);
+ server = _insecureServer;
+ }
+ server.listen((socket) {
+ final connection = new ServerTransportConnection.viaSocket(socket);
+ _connections.add(connection);
+ ServerHandler_ handler;
+ // TODO(jakobr): Set active state handlers, close connection after idle
+ // timeout.
+ connection.incomingStreams.listen((stream) {
+ handler = serveStream_(stream);
+ }, onError: (error) {
+ print('Connection error: $error');
+ }, onDone: () {
+ // TODO(sigurdm): This is not correct behavior in the presence of
+ // half-closed tcp streams.
+ // Half-closed streams seems to not be fully supported by package:http2.
+ // https://github.com/dart-lang/http2/issues/42
+ handler?.cancel();
+ _connections.remove(connection);
+ });
+ }, onError: (error) {
+ print('Socket error: $error');
+ });
+ }
+
+ @visibleForTesting
+ ServerHandler_ serveStream_(ServerTransportStream stream) {
+ return new ServerHandler_(lookupService, stream, _interceptors)..handle();
+ }
+
+ @Deprecated(
+ 'This is internal functionality, and will be removed in next major version.')
+ void serveStream(ServerTransportStream stream) {
+ serveStream_(stream);
+ }
+
+ Future<void> shutdown() async {
+ final done = _connections.map((connection) => connection.finish()).toList();
+ if (_insecureServer != null) {
+ done.add(_insecureServer.close());
+ }
+ if (_secureServer != null) {
+ done.add(_secureServer.close());
+ }
+ await Future.wait(done);
+ _insecureServer = null;
+ _secureServer = null;
+ }
+}
diff --git a/grpc/lib/src/server/service.dart b/grpc/lib/src/server/service.dart
new file mode 100644
index 0000000..26d869c
--- /dev/null
+++ b/grpc/lib/src/server/service.dart
@@ -0,0 +1,109 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:async';
+
+import '../shared/status.dart';
+import 'call.dart';
+
+/// Definition of a gRPC service method.
+class ServiceMethod<Q, R> {
+ final String name;
+
+ final bool streamingRequest;
+ final bool streamingResponse;
+
+ final Q Function(List<int> request) requestDeserializer;
+ final List<int> Function(R response) responseSerializer;
+
+ final Function handler;
+
+ ServiceMethod(
+ this.name,
+ this.handler,
+ this.streamingRequest,
+ this.streamingResponse,
+ this.requestDeserializer,
+ this.responseSerializer);
+
+ StreamController<Q> createRequestStream(StreamSubscription incoming) =>
+ new StreamController<Q>(
+ onListen: incoming.resume,
+ onPause: incoming.pause,
+ onResume: incoming.resume);
+
+ Q deserialize(List<int> data) => requestDeserializer(data);
+
+ List<int> serialize(dynamic response) => responseSerializer(response as R);
+
+ Stream<R> handle(ServiceCall call, Stream<Q> requests) {
+ if (streamingResponse) {
+ if (streamingRequest) {
+ return handler(call, requests);
+ } else {
+ return handler(call, _toSingleFuture(requests));
+ }
+ } else {
+ Future<R> response;
+ if (streamingRequest) {
+ response = handler(call, requests);
+ } else {
+ response = handler(call, _toSingleFuture(requests));
+ }
+ return response.asStream();
+ }
+ }
+
+ Future<Q> _toSingleFuture(Stream<Q> stream) {
+ Q _ensureOnlyOneRequest(Q previous, Q element) {
+ if (previous != null) {
+ throw new GrpcError.unimplemented('More than one request received');
+ }
+ return element;
+ }
+
+ Q _ensureOneRequest(Q value) {
+ if (value == null)
+ throw new GrpcError.unimplemented('No requests received');
+ return value;
+ }
+
+ final future =
+ stream.fold(null, _ensureOnlyOneRequest).then(_ensureOneRequest);
+ // Make sure errors on the future aren't unhandled, but return the original
+ // future so the request handler can also get the error.
+ future.catchError((_) {});
+ return future;
+ }
+}
+
+/// Definition of a gRPC service.
+abstract class Service {
+ final Map<String, ServiceMethod> _$methods = {};
+
+ String get $name;
+
+ void $addMethod(ServiceMethod method) {
+ _$methods[method.name] = method;
+ }
+
+ /// Client metadata handler.
+ ///
+ /// Services can override this method to provide common handling of incoming
+ /// metadata from the client.
+ void $onMetadata(ServiceCall context) {}
+
+ ServiceMethod $lookupMethod(String name) => _$methods[name];
+}
diff --git a/grpc/lib/src/shared/security.dart b/grpc/lib/src/shared/security.dart
new file mode 100644
index 0000000..2e09f37
--- /dev/null
+++ b/grpc/lib/src/shared/security.dart
@@ -0,0 +1,26 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:io';
+
+const supportedAlpnProtocols = const ['grpc-exp', 'h2'];
+
+// TODO: Simplify once we have a stable Dart 1.25 release (update pubspec to
+// require SDK >=1.25.0, and remove check for alpnSupported).
+SecurityContext createSecurityContext(bool isServer) =>
+ SecurityContext.alpnSupported
+ ? (new SecurityContext()
+ ..setAlpnProtocols(supportedAlpnProtocols, isServer))
+ : new SecurityContext();
diff --git a/grpc/lib/src/shared/status.dart b/grpc/lib/src/shared/status.dart
new file mode 100644
index 0000000..4879cd3
--- /dev/null
+++ b/grpc/lib/src/shared/status.dart
@@ -0,0 +1,244 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+class StatusCode {
+ /// The operation completed successfully.
+ static const ok = 0;
+
+ /// The operation was cancelled (typically by the caller).
+ static const cancelled = 1;
+
+ /// Unknown error. An example of where this error may be returned is if a
+ /// Status value received from another address space belongs to an error-space
+ /// that is not known in this address space. Also errors raised by APIs that
+ /// do not return enough error information may be converted to this error.
+ static const unknown = 2;
+
+ /// Client specified an invalid argument. Note that this differs from
+ /// [failedPrecondition]. [invalidArgument] indicates arguments that are
+ /// problematic regardless of the state of the system (e.g., a malformed file
+ /// name).
+ static const invalidArgument = 3;
+
+ /// Deadline expired before operation could complete. For operations that
+ /// change the state of the system, this error may be returned even if the
+ /// operation has completed successfully. For example, a successful response
+ /// from a server could have been delayed long enough for the deadline to
+ /// expire.
+ static const deadlineExceeded = 4;
+
+ /// Some requested entity (e.g., file or directory) was not found.
+ static const notFound = 5;
+
+ /// Some entity that we attempted to create (e.g., file or directory) already
+ /// exists.
+ static const alreadyExists = 6;
+
+ /// The caller does not have permission to execute the specified operation.
+ /// [permissionDenied] must not be used for rejections caused by exhausting
+ /// some resource (use [resourceExhausted] instead for those errors).
+ /// [permissionDenied] must not be used if the caller cannot be identified
+ /// (use [unauthenticated] instead for those errors).
+ static const permissionDenied = 7;
+
+ /// Some resource has been exhausted, perhaps a per-user quota, or perhaps the
+ /// entire file system is out of space.
+ static const resourceExhausted = 8;
+
+ /// Operation was rejected because the system is not in a state required for
+ /// the operation's execution. For example, directory to be deleted may be
+ /// non-empty, an rmdir operation is applied to a non-directory, etc.
+ ///
+ /// A litmus test that may help a service implementor in deciding between
+ /// [failedPrecondition], [aborted], and [unavailable]:
+ /// (a) Use [unavailable] if the client can retry just the failing call.
+ /// (b) Use [aborted] if the client should retry at a higher-level (e.g.,
+ /// restarting a read-modify-write sequence).
+ /// (c) Use [failedPrecondition] if the client should not retry until the
+ /// system state has been explicitly fixed. E.g., if an "rmdir" fails
+ /// because the directory is non-empty, [failedPrecondition] should be
+ /// returned since the client should not retry unless they have first
+ /// fixed up the directory by deleting files from it.
+ static const failedPrecondition = 9;
+
+ /// The operation was aborted, typically due to a concurrency issue like
+ /// sequencer check failures, transaction aborts, etc.
+ ///
+ /// See litmus test above for deciding between [failedPrecondition],
+ /// [aborted], and [unavailable].
+ static const aborted = 10;
+
+ /// Operation was attempted past the valid range. E.g., seeking or reading
+ /// past end of file.
+ ///
+ /// Unlike invalidArgument, this error indicates a problem that may be fixed
+ /// if the system state changes. For example, a 32-bit file system will
+ /// generate invalidArgument if asked to read at an offset that is not in the
+ /// range [0,2^32-1], but it will generate [outOfRange] if asked to read from
+ /// an offset past the current file size.
+ ///
+ /// There is a fair bit of overlap between [failedPrecondition] and
+ /// [outOfRange]. We recommend using [outOfRange] (the more specific error)
+ /// when it applies so that callers who are iterating through a space can
+ /// easily look for an [outOfRange] error to detect when they are done.
+ static const outOfRange = 11;
+
+ /// Operation is not implemented or not supported/enabled in this service.
+ static const unimplemented = 12;
+
+ /// Internal errors. Means some invariants expected by underlying system has
+ /// been broken. If you see one of these errors, something is very broken.
+ static const internal = 13;
+
+ /// The service is currently unavailable. This is a most likely a transient
+ /// condition and may be corrected by retrying with a backoff.
+ ///
+ /// See litmus test above for deciding between [failedPrecondition],
+ /// [aborted], and [unavailable].
+ static const unavailable = 14;
+
+ /// Unrecoverable data loss or corruption.
+ static const dataLoss = 15;
+
+ /// The request does not have valid authentication credentials for the
+ /// operation.
+ static const unauthenticated = 16;
+}
+
+class GrpcError {
+ final int code;
+ final String message;
+
+ /// Custom error code.
+ GrpcError.custom(this.code, [this.message]);
+
+ /// The operation completed successfully.
+ GrpcError.ok([this.message]) : code = StatusCode.ok;
+
+ /// The operation was cancelled (typically by the caller).
+ GrpcError.cancelled([this.message]) : code = StatusCode.cancelled;
+
+ /// Unknown error. An example of where this error may be returned is if a
+ /// Status value received from another address space belongs to an error-space
+ /// that is not known in this address space. Also errors raised by APIs that
+ /// do not return enough error information may be converted to this error.
+ GrpcError.unknown([this.message]) : code = StatusCode.unknown;
+
+ /// Client specified an invalid argument. Note that this differs from
+ /// [failedPrecondition]. [invalidArgument] indicates arguments that are
+ /// problematic regardless of the state of the system (e.g., a malformed file
+ /// name).
+ GrpcError.invalidArgument([this.message]) : code = StatusCode.invalidArgument;
+
+ /// Deadline expired before operation could complete. For operations that
+ /// change the state of the system, this error may be returned even if the
+ /// operation has completed successfully. For example, a successful response
+ /// from a server could have been delayed long enough for the deadline to
+ /// expire.
+ GrpcError.deadlineExceeded([this.message])
+ : code = StatusCode.deadlineExceeded;
+
+ /// Some requested entity (e.g., file or directory) was not found.
+ GrpcError.notFound([this.message]) : code = StatusCode.notFound;
+
+ /// Some entity that we attempted to create (e.g., file or directory) already
+ /// exists.
+ GrpcError.alreadyExists([this.message]) : code = StatusCode.alreadyExists;
+
+ /// The caller does not have permission to execute the specified operation.
+ /// [permissionDenied] must not be used for rejections caused by exhausting
+ /// some resource (use [resourceExhausted] instead for those errors).
+ /// [permissionDenied] must not be used if the caller cannot be identified
+ /// (use [unauthenticated] instead for those errors).
+ GrpcError.permissionDenied([this.message])
+ : code = StatusCode.permissionDenied;
+
+ /// Some resource has been exhausted, perhaps a per-user quota, or perhaps the
+ /// entire file system is out of space.
+ GrpcError.resourceExhausted([this.message])
+ : code = StatusCode.resourceExhausted;
+
+ /// Operation was rejected because the system is not in a state required for
+ /// the operation's execution. For example, directory to be deleted may be
+ /// non-empty, an rmdir operation is applied to a non-directory, etc.
+ ///
+ /// A litmus test that may help a service implementor in deciding between
+ /// [failedPrecondition], [aborted], and [unavailable]:
+ /// (a) Use [unavailable] if the client can retry just the failing call.
+ /// (b) Use [aborted] if the client should retry at a higher-level (e.g.,
+ /// restarting a read-modify-write sequence).
+ /// (c) Use [failedPrecondition] if the client should not retry until the
+ /// system state has been explicitly fixed. E.g., if an "rmdir" fails
+ /// because the directory is non-empty, [failedPrecondition] should be
+ /// returned since the client should not retry unless they have first
+ /// fixed up the directory by deleting files from it.
+ GrpcError.failedPrecondition([this.message])
+ : code = StatusCode.failedPrecondition;
+
+ /// The operation was aborted, typically due to a concurrency issue like
+ /// sequencer check failures, transaction aborts, etc.
+ ///
+ /// See litmus test above for deciding between [failedPrecondition],
+ /// [aborted], and [unavailable].
+ GrpcError.aborted([this.message]) : code = StatusCode.aborted;
+
+ /// Operation was attempted past the valid range. E.g., seeking or reading
+ /// past end of file.
+ ///
+ /// Unlike invalidArgument, this error indicates a problem that may be fixed
+ /// if the system state changes. For example, a 32-bit file system will
+ /// generate invalidArgument if asked to read at an offset that is not in the
+ /// range [0,2^32-1], but it will generate [outOfRange] if asked to read from
+ /// an offset past the current file size.
+ ///
+ /// There is a fair bit of overlap between [failedPrecondition] and
+ /// [outOfRange]. We recommend using [outOfRange] (the more specific error)
+ /// when it applies so that callers who are iterating through a space can
+ /// easily look for an [outOfRange] error to detect when they are done.
+ GrpcError.outOfRange([this.message]) : code = StatusCode.outOfRange;
+
+ /// Operation is not implemented or not supported/enabled in this service.
+ GrpcError.unimplemented([this.message]) : code = StatusCode.unimplemented;
+
+ /// Internal errors. Means some invariants expected by underlying system has
+ /// been broken. If you see one of these errors, something is very broken.
+ GrpcError.internal([this.message]) : code = StatusCode.internal;
+
+ /// The service is currently unavailable. This is a most likely a transient
+ /// condition and may be corrected by retrying with a backoff.
+ ///
+ /// See litmus test above for deciding between [failedPrecondition],
+ /// [aborted], and [unavailable].
+ GrpcError.unavailable([this.message]) : code = StatusCode.unavailable;
+
+ /// Unrecoverable data loss or corruption.
+ GrpcError.dataLoss([this.message]) : code = StatusCode.dataLoss;
+
+ /// The request does not have valid authentication credentials for the
+ /// operation.
+ GrpcError.unauthenticated([this.message]) : code = StatusCode.unauthenticated;
+
+ @override
+ bool operator ==(other) {
+ if (other is! GrpcError) return false;
+ return code == other.code && message == other.message;
+ }
+
+ @override
+ int get hashCode => code.hashCode ^ (message?.hashCode ?? 17);
+
+ @override
+ String toString() => 'gRPC Error ($code, $message)';
+}
diff --git a/grpc/lib/src/shared/streams.dart b/grpc/lib/src/shared/streams.dart
new file mode 100644
index 0000000..7e3148b
--- /dev/null
+++ b/grpc/lib/src/shared/streams.dart
@@ -0,0 +1,204 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:math';
+import 'dart:typed_data';
+
+import 'package:http2/transport.dart';
+
+import 'status.dart';
+
+abstract class GrpcMessage {}
+
+class GrpcMetadata extends GrpcMessage {
+ final Map<String, String> metadata;
+ GrpcMetadata(this.metadata);
+
+ @override
+ String toString() => 'gRPC Metadata ($metadata)';
+}
+
+class GrpcData extends GrpcMessage {
+ final List<int> data;
+ final bool isCompressed;
+ GrpcData(this.data, {this.isCompressed});
+
+ @override
+ String toString() => 'gRPC Data (${data.length} bytes)';
+}
+
+StreamTransformer<GrpcMessage, GrpcMessage> grpcDecompressor() =>
+ new StreamTransformer<GrpcMessage, GrpcMessage>.fromHandlers(
+ handleData: (GrpcMessage value, EventSink<GrpcMessage> sink) {
+ if (value is GrpcData) {
+ if (value.isCompressed) {
+ // TODO(dart-lang/grpc-dart#6): Actually handle decompression.
+ sink.add(new GrpcData(value.data, isCompressed: false));
+ return;
+ }
+ }
+ sink.add(value);
+ });
+
+class GrpcHttpEncoder extends Converter<GrpcMessage, StreamMessage> {
+ @override
+ StreamMessage convert(GrpcMessage input) {
+ if (input is GrpcMetadata) {
+ final headers = <Header>[];
+ input.metadata.forEach((key, value) {
+ headers.add(new Header(ascii.encode(key), utf8.encode(value)));
+ });
+ return new HeadersStreamMessage(headers);
+ } else if (input is GrpcData) {
+ return new DataStreamMessage(frame(input.data));
+ }
+ throw new GrpcError.internal('Unexpected message type');
+ }
+
+ static List<int> frame(List<int> payload) {
+ final payloadLength = payload.length;
+ final bytes = new Uint8List(payloadLength + 5);
+ final header = bytes.buffer.asByteData(0, 5);
+ header.setUint8(0, 0); // TODO(dart-lang/grpc-dart#6): Handle compression
+ header.setUint32(1, payloadLength);
+ bytes.setRange(5, bytes.length, payload);
+ return bytes;
+ }
+}
+
+class GrpcHttpDecoder extends Converter<StreamMessage, GrpcMessage> {
+ @override
+ GrpcMessage convert(StreamMessage input) {
+ final sink = new _GrpcMessageSink();
+ startChunkedConversion(sink)
+ ..add(input)
+ ..close();
+ return sink.message;
+ }
+
+ @override
+ Sink<StreamMessage> startChunkedConversion(Sink<GrpcMessage> sink) {
+ return new _GrpcMessageConversionSink(sink);
+ }
+}
+
+class _GrpcMessageConversionSink extends ChunkedConversionSink<StreamMessage> {
+ final Sink<GrpcMessage> _out;
+
+ final _dataHeader = new Uint8List(5);
+ Uint8List _data;
+ int _dataOffset = 0;
+
+ _GrpcMessageConversionSink(this._out);
+
+ void _addData(DataStreamMessage chunk) {
+ final chunkData = chunk.bytes;
+ final chunkLength = chunkData.length;
+ var chunkReadOffset = 0;
+
+ while (chunkReadOffset < chunkLength) {
+ if (_data == null) {
+ // Reading header.
+ final headerRemaining = _dataHeader.lengthInBytes - _dataOffset;
+ final chunkRemaining = chunkLength - chunkReadOffset;
+ final toCopy = min(headerRemaining, chunkRemaining);
+ _dataHeader.setRange(
+ _dataOffset, _dataOffset + toCopy, chunkData, chunkReadOffset);
+ _dataOffset += toCopy;
+ chunkReadOffset += toCopy;
+ if (_dataOffset == _dataHeader.lengthInBytes) {
+ final dataLength = _dataHeader.buffer.asByteData().getUint32(1);
+ // TODO(jakobr): Sanity check dataLength. Max size?
+ _data = new Uint8List(dataLength);
+ _dataOffset = 0;
+ }
+ }
+ if (_data != null) {
+ // Reading data.
+ final dataRemaining = _data.lengthInBytes - _dataOffset;
+ if (dataRemaining > 0) {
+ final chunkRemaining = chunkLength - chunkReadOffset;
+ final toCopy = min(dataRemaining, chunkRemaining);
+ _data.setRange(
+ _dataOffset, _dataOffset + toCopy, chunkData, chunkReadOffset);
+ _dataOffset += toCopy;
+ chunkReadOffset += toCopy;
+ }
+ if (_dataOffset == _data.lengthInBytes) {
+ _out.add(new GrpcData(_data,
+ isCompressed: _dataHeader.buffer.asByteData().getUint8(0) != 0));
+ _data = null;
+ _dataOffset = 0;
+ }
+ }
+ }
+ }
+
+ void _addHeaders(HeadersStreamMessage chunk) {
+ if (_data != null || _dataOffset != 0) {
+ // We were in the middle of receiving data, so receiving a header frame
+ // is a violation of the gRPC protocol.
+ throw new GrpcError.unimplemented('Received header while reading data');
+ }
+ final headers = <String, String>{};
+ for (var header in chunk.headers) {
+ // TODO(jakobr): Handle duplicate header names correctly.
+ headers[ascii.decode(header.name)] = ascii.decode(header.value);
+ }
+ // TODO(jakobr): Check :status, go to error mode if not 2xx.
+ _out.add(new GrpcMetadata(headers));
+ }
+
+ @override
+ void add(StreamMessage chunk) {
+ if (chunk is DataStreamMessage) {
+ _addData(chunk);
+ } else if (chunk is HeadersStreamMessage) {
+ _addHeaders(chunk);
+ } else {
+ // No clue what this is.
+ throw new GrpcError.unimplemented('Received unknown HTTP/2 frame type');
+ }
+ }
+
+ @override
+ void close() {
+ if (_data != null || _dataOffset != 0) {
+ throw new GrpcError.unavailable('Closed in non-idle state');
+ }
+ _out.close();
+ }
+}
+
+class _GrpcMessageSink extends Sink<GrpcMessage> {
+ GrpcMessage message;
+
+ @override
+ void add(GrpcMessage data) {
+ if (message != null) {
+ throw 'Too many messages received!';
+ }
+ message = data;
+ }
+
+ @override
+ void close() {
+ if (message == null) {
+ throw 'No messages received!';
+ }
+ }
+}
diff --git a/grpc/lib/src/shared/timeout.dart b/grpc/lib/src/shared/timeout.dart
new file mode 100644
index 0000000..965ab88
--- /dev/null
+++ b/grpc/lib/src/shared/timeout.dart
@@ -0,0 +1,62 @@
+// Copyright (c) 2017, the gRPC project authors. Please see the AUTHORS file
+// for details. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/// Convert [timeout] to grpc-timeout header string format.
+// Mostly inspired by grpc-java implementation.
+// TODO(jakobr): Modify to match grpc/core implementation instead.
+String toTimeoutString(Duration duration) {
+ if (duration == null) return null;
+ const cutoff = 100000;
+ final timeout = duration.inMicroseconds;
+ if (timeout < 0) {
+ // Smallest possible timeout.
+ return '1n';
+ } else if (timeout < cutoff) {
+ return '${timeout}u';
+ } else if (timeout < cutoff * 1000) {
+ return '${timeout ~/ 1000}m';
+ } else if (timeout < cutoff * 1000 * 1000) {
+ return '${timeout ~/ 1000000}S';
+ } else if (timeout < cutoff * 1000 * 1000 * 60) {
+ return '${timeout ~/ 60000000}M';
+ } else {
+ return '${timeout ~/ 3600000000}H';
+ }
+}
+
+/// Convert [timeout] from grpc-timeout header string format to [Duration].
+/// Returns [null] if [timeout] is not correctly formatted.
+Duration fromTimeoutString(String timeout) {
+ if (timeout == null) return null;
+ if (timeout.length < 2) return null;
+ final value = int.tryParse(timeout.substring(0, timeout.length - 1));
+ if (value == null) return null;
+ switch (timeout[timeout.length - 1]) {
+ case 'n':
+ return new Duration(microseconds: value * 1000);
+ case 'u':
+ return new Duration(microseconds: value);
+ case 'm':
+ return new Duration(milliseconds: value);
+ case 'S':
+ return new Duration(seconds: value);
+ case 'M':
+ return new Duration(minutes: value);
+ case 'H':
+ return new Duration(hours: value);
+ default:
+ return null;
+ }
+}
diff --git a/grpc/pubspec.yaml b/grpc/pubspec.yaml
new file mode 100644
index 0000000..341ab8f
--- /dev/null
+++ b/grpc/pubspec.yaml
@@ -0,0 +1,19 @@
+name: grpc
+description: Dart implementation of gRPC, a high performance, open-source universal RPC framework.
+version: 1.0.3
+author: Dart Team <misc@dartlang.org>
+homepage: https://github.com/dart-lang/grpc-dart
+
+environment:
+ sdk: '>=2.0.0 <3.0.0'
+
+dependencies:
+ async: '>=1.13.3 <3.0.0'
+ googleapis_auth: ^0.2.5+3
+ meta: ^1.0.5
+ http: '>=0.11.3+17 <0.13.0'
+ http2: '>=0.1.7 <2.0.0'
+
+dev_dependencies:
+ mockito: ^4.0.0
+ test: ^1.6.1
diff --git a/http2/.dart_tool/pub/bin/sdk-version b/http2/.dart_tool/pub/bin/sdk-version
new file mode 100644
index 0000000..227cea2
--- /dev/null
+++ b/http2/.dart_tool/pub/bin/sdk-version
@@ -0,0 +1 @@
+2.0.0
diff --git a/http2/.dart_tool/pub/bin/test/test.dart.snapshot.dart2 b/http2/.dart_tool/pub/bin/test/test.dart.snapshot.dart2
new file mode 100644
index 0000000..bea517b
--- /dev/null
+++ b/http2/.dart_tool/pub/bin/test/test.dart.snapshot.dart2
Binary files differ
diff --git a/http2/.gitignore b/http2/.gitignore
new file mode 100644
index 0000000..a19c373
--- /dev/null
+++ b/http2/.gitignore
@@ -0,0 +1,16 @@
+# Don’t commit the following directories created by pub.
+build/
+.packages
+.pub/
+packages
+.buildlog
+
+# Or the files created by dart2js.
+*.dart.js
+*.dart.precompiled.js
+*.js_
+*.js.deps
+*.js.map
+
+# Include when developing application packages.
+pubspec.lock
diff --git a/http2/.idea/codeStyles/codeStyleConfig.xml b/http2/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..c79f34c
--- /dev/null
+++ b/http2/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+<component name="ProjectCodeStyleConfiguration">
+ <state>
+ <option name="PREFERRED_PROJECT_CODE_STYLE" value="Google Configuration Checker Style" />
+ </state>
+</component>
\ No newline at end of file
diff --git a/http2/.idea/http2.iml b/http2/.idea/http2.iml
new file mode 100644
index 0000000..ae9af97
--- /dev/null
+++ b/http2/.idea/http2.iml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <excludeFolder url="file://$MODULE_DIR$/.dart_tool" />
+ <excludeFolder url="file://$MODULE_DIR$/.pub" />
+ <excludeFolder url="file://$MODULE_DIR$/build" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="library" name="Dart SDK" level="project" />
+ <orderEntry type="library" name="Dart Packages" level="project" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/http2/.idea/libraries/Dart_Packages.xml b/http2/.idea/libraries/Dart_Packages.xml
new file mode 100644
index 0000000..bb6094d
--- /dev/null
+++ b/http2/.idea/libraries/Dart_Packages.xml
@@ -0,0 +1,428 @@
+<component name="libraryTable">
+ <library name="Dart Packages" type="DartPackagesLibraryType">
+ <properties>
+ <option name="packageNameToDirsMap">
+ <entry key="analyzer">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/analyzer-0.33.3+1/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="args">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/args-1.5.1/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="async">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/async-2.0.8/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="boolean_selector">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/boolean_selector-1.0.4/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="charcode">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/charcode-1.1.2/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="collection">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/collection-1.14.11/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="convert">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/convert-2.0.2/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="crypto">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/crypto-2.0.6/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="csslib">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/csslib-0.14.6/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="front_end">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/front_end-0.1.6+4/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="glob">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/glob-1.1.7/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="html">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/html-0.13.3+3/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="http">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/http-0.12.0/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="http_multi_server">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/http_multi_server-2.0.5/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="http_parser">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/http_parser-3.1.3/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="io">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/io-0.3.3/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="js">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/js-0.6.1+1/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="json_rpc_2">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/json_rpc_2-2.0.9/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="kernel">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/kernel-0.3.6+4/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="logging">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/logging-0.11.3+2/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="matcher">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.3+1/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="meta">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/meta-1.1.6/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="mime">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/mime-0.9.6+2/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="mockito">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/mockito-4.0.0/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="multi_server_socket">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/multi_server_socket-1.0.2/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="node_preamble">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/node_preamble-1.4.4/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="package_config">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/package_config-1.0.5/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="package_resolver">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/package_resolver-1.0.6/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="path">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/path-1.6.2/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="plugin">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/plugin-0.2.0+3/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="pool">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/pool-1.3.6/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="pub_semver">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/pub_semver-1.4.2/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="shelf">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/shelf-0.7.3+3/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="shelf_packages_handler">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/shelf_packages_handler-1.0.4/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="shelf_static">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/shelf_static-0.2.8/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="shelf_web_socket">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/shelf_web_socket-0.2.2+4/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="source_map_stack_trace">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/source_map_stack_trace-1.1.5/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="source_maps">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/source_maps-0.10.8/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="source_span">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/source_span-1.4.1/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="stack_trace">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.9.3/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="stream_channel">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/stream_channel-1.6.8/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="string_scanner">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.0.4/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="term_glyph">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.0.1/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="test">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/test-1.5.1+1/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="test_api">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/test_api-0.2.1/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="test_core">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/test_core-0.2.0+1/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="typed_data">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/typed_data-1.1.6/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="utf">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/utf-0.9.0+5/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="vm_service_client">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/vm_service_client-0.2.6/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="watcher">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/watcher-0.9.7+10/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="web_socket_channel">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/web_socket_channel-1.0.9/lib" />
+ </list>
+ </value>
+ </entry>
+ <entry key="yaml">
+ <value>
+ <list>
+ <option value="$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/yaml-2.1.15/lib" />
+ </list>
+ </value>
+ </entry>
+ </option>
+ </properties>
+ <CLASSES>
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/analyzer-0.33.3+1/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/args-1.5.1/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/async-2.0.8/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/boolean_selector-1.0.4/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/charcode-1.1.2/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/collection-1.14.11/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/convert-2.0.2/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/crypto-2.0.6/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/csslib-0.14.6/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/front_end-0.1.6+4/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/glob-1.1.7/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/html-0.13.3+3/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/http-0.12.0/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/http_multi_server-2.0.5/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/http_parser-3.1.3/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/io-0.3.3/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/js-0.6.1+1/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/json_rpc_2-2.0.9/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/kernel-0.3.6+4/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/logging-0.11.3+2/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/matcher-0.12.3+1/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/meta-1.1.6/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/mime-0.9.6+2/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/mockito-4.0.0/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/multi_server_socket-1.0.2/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/node_preamble-1.4.4/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/package_config-1.0.5/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/package_resolver-1.0.6/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/path-1.6.2/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/plugin-0.2.0+3/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/pool-1.3.6/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/pub_semver-1.4.2/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/shelf-0.7.3+3/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/shelf_packages_handler-1.0.4/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/shelf_static-0.2.8/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/shelf_web_socket-0.2.2+4/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/source_map_stack_trace-1.1.5/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/source_maps-0.10.8/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/source_span-1.4.1/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/stack_trace-1.9.3/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/stream_channel-1.6.8/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/string_scanner-1.0.4/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/term_glyph-1.0.1/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/test-1.5.1+1/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/test_api-0.2.1/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/test_core-0.2.0+1/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/typed_data-1.1.6/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/utf-0.9.0+5/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/vm_service_client-0.2.6/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/watcher-0.9.7+10/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/web_socket_channel-1.0.9/lib" />
+ <root url="file://$USER_HOME$/.pub-cache/hosted/pub.dartlang.org/yaml-2.1.15/lib" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+</component>
\ No newline at end of file
diff --git a/http2/.idea/libraries/Dart_SDK.xml b/http2/.idea/libraries/Dart_SDK.xml
new file mode 100644
index 0000000..e2b2cff
--- /dev/null
+++ b/http2/.idea/libraries/Dart_SDK.xml
@@ -0,0 +1,27 @@
+<component name="libraryTable">
+ <library name="Dart SDK">
+ <CLASSES>
+ <root url="file://$USER_HOME$/install/dart-sdk/lib/async" />
+ <root url="file://$USER_HOME$/install/dart-sdk/lib/cli" />
+ <root url="file://$USER_HOME$/install/dart-sdk/lib/collection" />
+ <root url="file://$USER_HOME$/install/dart-sdk/lib/convert" />
+ <root url="file://$USER_HOME$/install/dart-sdk/lib/core" />
+ <root url="file://$USER_HOME$/install/dart-sdk/lib/developer" />
+ <root url="file://$USER_HOME$/install/dart-sdk/lib/html" />
+ <root url="file://$USER_HOME$/install/dart-sdk/lib/indexed_db" />
+ <root url="file://$USER_HOME$/install/dart-sdk/lib/io" />
+ <root url="file://$USER_HOME$/install/dart-sdk/lib/isolate" />
+ <root url="file://$USER_HOME$/install/dart-sdk/lib/js" />
+ <root url="file://$USER_HOME$/install/dart-sdk/lib/js_util" />
+ <root url="file://$USER_HOME$/install/dart-sdk/lib/math" />
+ <root url="file://$USER_HOME$/install/dart-sdk/lib/mirrors" />
+ <root url="file://$USER_HOME$/install/dart-sdk/lib/svg" />
+ <root url="file://$USER_HOME$/install/dart-sdk/lib/typed_data" />
+ <root url="file://$USER_HOME$/install/dart-sdk/lib/web_audio" />
+ <root url="file://$USER_HOME$/install/dart-sdk/lib/web_gl" />
+ <root url="file://$USER_HOME$/install/dart-sdk/lib/web_sql" />
+ </CLASSES>
+ <JAVADOC />
+ <SOURCES />
+ </library>
+</component>
\ No newline at end of file
diff --git a/http2/.idea/modules.xml b/http2/.idea/modules.xml
new file mode 100644
index 0000000..b9f6041
--- /dev/null
+++ b/http2/.idea/modules.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ProjectModuleManager">
+ <modules>
+ <module fileurl="file://$PROJECT_DIR$/.idea/http2.iml" filepath="$PROJECT_DIR$/.idea/http2.iml" />
+ </modules>
+ </component>
+</project>
\ No newline at end of file
diff --git a/http2/.idea/vcs.xml b/http2/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1ddf
--- /dev/null
+++ b/http2/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="VcsDirectoryMappings">
+ <mapping directory="" vcs="Git" />
+ </component>
+</project>
\ No newline at end of file
diff --git a/http2/.idea/workspace.xml b/http2/.idea/workspace.xml
new file mode 100644
index 0000000..4f75e60
--- /dev/null
+++ b/http2/.idea/workspace.xml
@@ -0,0 +1,806 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+ <component name="ChangeListManager">
+ <list default="true" id="ac17af96-834b-47c0-bec3-b5f93f5e3dc2" name="Default" comment="" />
+ <option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
+ <option name="TRACKING_ENABLED" value="true" />
+ <option name="SHOW_DIALOG" value="false" />
+ <option name="HIGHLIGHT_CONFLICTS" value="true" />
+ <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
+ <option name="LAST_RESOLUTION" value="IGNORE" />
+ </component>
+ <component name="FileEditorManager">
+ <leaf SIDE_TABS_SIZE_LIMIT_KEY="300">
+ <file leaf-file-name="transport.dart" pinned="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/lib/transport.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="15">
+ <caret line="1" column="12" selection-start-line="1" selection-start-column="12" selection-end-line="1" selection-end-column="12" />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="multiprotocol_server.dart" pinned="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/lib/multiprotocol_server.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="CHANGELOG.md" pinned="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/CHANGELOG.md">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="75">
+ <caret line="5" column="48" selection-start-line="5" selection-start-column="48" selection-end-line="5" selection-end-column="48" />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="README.md" pinned="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/README.md">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="13">
+ <caret line="13" column="27" selection-start-line="13" selection-start-column="27" selection-end-line="13" selection-end-column="27" />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="pubspec.yaml" pinned="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/pubspec.yaml">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="150">
+ <caret line="10" column="17" selection-start-line="10" selection-start-column="17" selection-end-line="10" selection-end-column="17" />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="out_of_stream_ids_test.dart" pinned="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/manual_test/out_of_stream_ids_test.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state>
+ <folding>
+ <element signature="e#0#2139#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="transport_test.dart" pinned="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/test/transport_test.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="333">
+ <caret line="512" column="9" selection-start-line="512" selection-start-column="9" selection-end-line="512" selection-end-column="9" />
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="http2.dart" pinned="false" current-in-tab="true">
+ <entry file="file://$PROJECT_DIR$/lib/http2.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="720">
+ <caret line="48" lean-forward="true" selection-start-line="48" selection-end-line="48" />
+ <folding>
+ <element signature="e#0#2418#0" expanded="true" />
+ <element signature="e#2364#2388#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ </file>
+ <file leaf-file-name="codereview.settings" pinned="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/codereview.settings">
+ <provider selected="true" editor-type-id="text-editor" />
+ </entry>
+ </file>
+ <file leaf-file-name="client.dart" pinned="false" current-in-tab="false">
+ <entry file="file://$PROJECT_DIR$/lib/src/testing/client.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ <folding>
+ <element signature="e#217#237#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ </file>
+ </leaf>
+ </component>
+ <component name="FindInProjectRecents">
+ <findStrings>
+ <find>therefore not be used to make new streams</find>
+ <find>no longer active</find>
+ <find>StreamMessageQueueIn</find>
+ <find>The http/2 connection</find>
+ <find>nego</find>
+ <find>library</find>
+ </findStrings>
+ </component>
+ <component name="Git.Settings">
+ <option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
+ </component>
+ <component name="IdeDocumentHistory">
+ <option name="CHANGED_PATHS">
+ <list>
+ <option value="$PROJECT_DIR$/example/request_resource.dart" />
+ <option value="$PROJECT_DIR$/example/minimal.dart" />
+ <option value="$PROJECT_DIR$/example/display_headers.dart" />
+ <option value="$PROJECT_DIR$/README.md" />
+ <option value="$PROJECT_DIR$/lib/transport.dart" />
+ <option value="$PROJECT_DIR$/lib/src/error_handler.dart" />
+ <option value="$PROJECT_DIR$/lib/multiprotocol_server.dart" />
+ <option value="$PROJECT_DIR$/lib/src/artificial_server_socket.dart" />
+ <option value="$PROJECT_DIR$/lib/src/byte_utils.dart" />
+ <option value="$PROJECT_DIR$/lib/src/connection.dart" />
+ <option value="$PROJECT_DIR$/lib/src/connection_preface.dart" />
+ <option value="$PROJECT_DIR$/lib/src/sync_errors.dart" />
+ <option value="$PROJECT_DIR$/lib/src/async_utils/async_utils.dart" />
+ <option value="$PROJECT_DIR$/lib/src/flowcontrol/connection_queues.dart" />
+ <option value="$PROJECT_DIR$/lib/src/flowcontrol/stream_queues.dart" />
+ <option value="$PROJECT_DIR$/lib/src/flowcontrol/window.dart" />
+ <option value="$PROJECT_DIR$/lib/src/flowcontrol/window_handler.dart" />
+ <option value="$PROJECT_DIR$/lib/src/frames/frame_defragmenter.dart" />
+ <option value="$PROJECT_DIR$/lib/src/frames/frames.dart" />
+ <option value="$PROJECT_DIR$/lib/src/hpack/hpack.dart" />
+ <option value="$PROJECT_DIR$/lib/src/hpack/huffman.dart" />
+ <option value="$PROJECT_DIR$/lib/src/hpack/huffman_table.dart" />
+ <option value="$PROJECT_DIR$/lib/src/ping/ping_handler.dart" />
+ <option value="$PROJECT_DIR$/lib/src/settings/settings.dart" />
+ <option value="$PROJECT_DIR$/lib/src/streams/stream_handler.dart" />
+ <option value="$PROJECT_DIR$/lib/src/testing/debug.dart" />
+ <option value="$PROJECT_DIR$/test/client_test.dart" />
+ <option value="$PROJECT_DIR$/test/client_websites_test.dart" />
+ <option value="$PROJECT_DIR$/test/multiprotocol_server_test.dart" />
+ <option value="$PROJECT_DIR$/test/server_test.dart" />
+ <option value="$PROJECT_DIR$/test/src/connection_preface_test.dart" />
+ <option value="$PROJECT_DIR$/test/src/error_matchers.dart" />
+ <option value="$PROJECT_DIR$/test/src/streams/helper.dart" />
+ <option value="$PROJECT_DIR$/test/src/streams/simple_flow_test.dart" />
+ <option value="$PROJECT_DIR$/test/src/streams/simple_push_test.dart" />
+ <option value="$PROJECT_DIR$/test/src/streams/streams_test.dart" />
+ <option value="$PROJECT_DIR$/lib/src/testing/client.dart" />
+ <option value="$PROJECT_DIR$/pubspec.yaml" />
+ <option value="$PROJECT_DIR$/CHANGELOG.md" />
+ <option value="$PROJECT_DIR$/lib/http2.dart" />
+ </list>
+ </option>
+ </component>
+ <component name="ProjectFrameBounds">
+ <option name="x" value="447" />
+ <option name="y" value="54" />
+ <option name="width" value="1831" />
+ <option name="height" value="1435" />
+ </component>
+ <component name="ProjectLevelVcsManager">
+ <ConfirmationsSetting value="1" id="Add" />
+ </component>
+ <component name="ProjectView">
+ <navigator proportions="" version="1">
+ <foldersAlwaysOnTop value="true" />
+ </navigator>
+ <panes>
+ <pane id="ProjectPane">
+ <subPane>
+ <expand>
+ <path>
+ <item name="http2" type="b2602c69:ProjectViewProjectNode" />
+ <item name="http2" type="462c0819:PsiDirectoryNode" />
+ </path>
+ <path>
+ <item name="http2" type="b2602c69:ProjectViewProjectNode" />
+ <item name="http2" type="462c0819:PsiDirectoryNode" />
+ <item name="doc" type="462c0819:PsiDirectoryNode" />
+ </path>
+ <path>
+ <item name="http2" type="b2602c69:ProjectViewProjectNode" />
+ <item name="http2" type="462c0819:PsiDirectoryNode" />
+ <item name="doc" type="462c0819:PsiDirectoryNode" />
+ <item name="api" type="462c0819:PsiDirectoryNode" />
+ </path>
+ <path>
+ <item name="http2" type="b2602c69:ProjectViewProjectNode" />
+ <item name="http2" type="462c0819:PsiDirectoryNode" />
+ <item name="example" type="462c0819:PsiDirectoryNode" />
+ </path>
+ <path>
+ <item name="http2" type="b2602c69:ProjectViewProjectNode" />
+ <item name="http2" type="462c0819:PsiDirectoryNode" />
+ <item name="lib" type="462c0819:PsiDirectoryNode" />
+ </path>
+ <path>
+ <item name="http2" type="b2602c69:ProjectViewProjectNode" />
+ <item name="http2" type="462c0819:PsiDirectoryNode" />
+ <item name="lib" type="462c0819:PsiDirectoryNode" />
+ <item name="src" type="462c0819:PsiDirectoryNode" />
+ </path>
+ <path>
+ <item name="http2" type="b2602c69:ProjectViewProjectNode" />
+ <item name="http2" type="462c0819:PsiDirectoryNode" />
+ <item name="manual_test" type="462c0819:PsiDirectoryNode" />
+ </path>
+ <path>
+ <item name="http2" type="b2602c69:ProjectViewProjectNode" />
+ <item name="http2" type="462c0819:PsiDirectoryNode" />
+ <item name="test" type="462c0819:PsiDirectoryNode" />
+ </path>
+ </expand>
+ <select />
+ </subPane>
+ </pane>
+ <pane id="PackagesPane" />
+ <pane id="Scope" />
+ <pane id="AndroidView" />
+ </panes>
+ </component>
+ <component name="PropertiesComponent">
+ <property name="dart.analysis.tool.window.force.activate" value="false" />
+ <property name="last_opened_file_path" value="$PROJECT_DIR$" />
+ </component>
+ <component name="RunDashboard">
+ <option name="ruleStates">
+ <list>
+ <RuleState>
+ <option name="name" value="ConfigurationTypeDashboardGroupingRule" />
+ </RuleState>
+ <RuleState>
+ <option name="name" value="StatusDashboardGroupingRule" />
+ </RuleState>
+ </list>
+ </option>
+ </component>
+ <component name="RunManager" selected="Dart Test.tests in http2">
+ <configuration default="true" type="Application" factoryName="Application">
+ <option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
+ </configuration>
+ <configuration name="display_headers.dart" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" temporary="true" nameIsGenerated="true">
+ <option name="filePath" value="$PROJECT_DIR$/example/display_headers.dart" />
+ <option name="workingDirectory" value="$PROJECT_DIR$" />
+ </configuration>
+ <configuration name="minimal.dart" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" temporary="true" nameIsGenerated="true">
+ <option name="filePath" value="$PROJECT_DIR$/example/minimal.dart" />
+ <option name="workingDirectory" value="$PROJECT_DIR$" />
+ </configuration>
+ <configuration name="request_resource.dart" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application" temporary="true" nameIsGenerated="true">
+ <option name="filePath" value="$PROJECT_DIR$/example/request_resource.dart" />
+ <option name="workingDirectory" value="$PROJECT_DIR$" />
+ </configuration>
+ <configuration name="tests in http2" type="DartTestRunConfigurationType" factoryName="Dart Test" temporary="true" nameIsGenerated="true">
+ <option name="filePath" value="$PROJECT_DIR$" />
+ <option name="scope" value="FOLDER" />
+ </configuration>
+ <configuration default="true" type="JUnit" factoryName="JUnit">
+ <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+ <option name="ALTERNATIVE_JRE_PATH" />
+ <option name="PACKAGE_NAME" />
+ <option name="MAIN_CLASS_NAME" />
+ <option name="METHOD_NAME" />
+ <option name="TEST_OBJECT" value="class" />
+ <option name="VM_PARAMETERS" value="-ea" />
+ <option name="PARAMETERS" />
+ <option name="WORKING_DIRECTORY" value="%MODULE_WORKING_DIR%" />
+ <option name="PASS_PARENT_ENVS" value="true" />
+ <option name="TEST_SEARCH_SCOPE">
+ <value defaultName="singleModule" />
+ </option>
+ <patterns />
+ </configuration>
+ <configuration default="true" type="TestNG" factoryName="TestNG">
+ <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
+ <option name="ALTERNATIVE_JRE_PATH" />
+ <option name="SUITE_NAME" />
+ <option name="PACKAGE_NAME" />
+ <option name="MAIN_CLASS_NAME" />
+ <option name="METHOD_NAME" />
+ <option name="GROUP_NAME" />
+ <option name="TEST_OBJECT" value="CLASS" />
+ <option name="VM_PARAMETERS" value="-ea" />
+ <option name="PARAMETERS" />
+ <option name="WORKING_DIRECTORY" value="%MODULE_WORKING_DIR%" />
+ <option name="OUTPUT_DIRECTORY" />
+ <option name="PASS_PARENT_ENVS" value="true" />
+ <option name="TEST_SEARCH_SCOPE">
+ <value defaultName="singleModule" />
+ </option>
+ <option name="USE_DEFAULT_REPORTERS" value="false" />
+ <option name="PROPERTIES_FILE" />
+ <properties />
+ <listeners />
+ </configuration>
+ <list>
+ <item itemvalue="Dart Command Line App.request_resource.dart" />
+ <item itemvalue="Dart Command Line App.display_headers.dart" />
+ <item itemvalue="Dart Command Line App.minimal.dart" />
+ <item itemvalue="Dart Test.tests in http2" />
+ </list>
+ <recent_temporary>
+ <list>
+ <item itemvalue="Dart Test.tests in http2" />
+ <item itemvalue="Dart Command Line App.minimal.dart" />
+ <item itemvalue="Dart Command Line App.display_headers.dart" />
+ <item itemvalue="Dart Command Line App.request_resource.dart" />
+ </list>
+ </recent_temporary>
+ </component>
+ <component name="SvnConfiguration">
+ <configuration />
+ </component>
+ <component name="TaskManager">
+ <task active="true" id="Default" summary="Default task">
+ <changelist id="ac17af96-834b-47c0-bec3-b5f93f5e3dc2" name="Default" comment="" />
+ <created>1540293392764</created>
+ <option name="number" value="Default" />
+ <option name="presentableId" value="Default" />
+ <updated>1540293392764</updated>
+ </task>
+ <servers />
+ </component>
+ <component name="TestHistory">
+ <history-entry file="tests_in_http2 - 2018.12.13 at 14h 57m 07s.xml">
+ <configuration name="tests in http2" configurationId="DartTestRunConfigurationType" />
+ </history-entry>
+ </component>
+ <component name="ToolWindowManager">
+ <frame x="447" y="54" width="1831" height="1435" extended-state="0" />
+ <layout>
+ <window_info anchor="right" id="Palette" order="3" />
+ <window_info anchor="bottom" id="TODO" order="6" />
+ <window_info anchor="bottom" id="Messages" weight="0.32981133" />
+ <window_info anchor="right" id="Palette	" order="3" />
+ <window_info id="Image Layers" order="2" />
+ <window_info anchor="right" id="Capture Analysis" order="3" />
+ <window_info anchor="bottom" id="Event Log" order="7" side_tool="true" />
+ <window_info anchor="right" id="Maven Projects" order="3" />
+ <window_info anchor="bottom" id="Dart Analysis" order="7" weight="0.32981133" />
+ <window_info anchor="bottom" id="Run" order="2" weight="0.35698113" />
+ <window_info anchor="bottom" id="Version Control" order="7" weight="0.60075474" />
+ <window_info anchor="bottom" id="Code Review" order="7" />
+ <window_info active="true" anchor="bottom" id="Terminal" order="7" visible="true" weight="0.2792453" />
+ <window_info id="Capture Tool" order="2" />
+ <window_info id="Designer" order="2" />
+ <window_info content_ui="combo" id="Project" order="0" visible="true" weight="0.24986331" />
+ <window_info anchor="bottom" id="Find" order="1" weight="0.15773585" />
+ <window_info id="Structure" order="1" side_tool="true" weight="0.25" />
+ <window_info anchor="right" id="Ant Build" order="1" weight="0.25" />
+ <window_info id="UI Designer" order="2" />
+ <window_info anchor="right" id="Theme Preview" order="3" />
+ <window_info anchor="bottom" id="Debug" order="3" weight="0.4" />
+ <window_info id="Favorites" order="2" side_tool="true" />
+ <window_info anchor="right" content_ui="combo" id="Hierarchy" order="2" weight="0.25" />
+ <window_info anchor="bottom" id="Inspection" order="5" weight="0.4" />
+ <window_info anchor="right" id="Commander" internal_type="SLIDING" order="0" type="SLIDING" weight="0.4" />
+ <window_info anchor="bottom" id="Message" order="0" />
+ <window_info anchor="bottom" id="Cvs" order="4" weight="0.25" />
+ </layout>
+ </component>
+ <component name="VcsContentAnnotationSettings">
+ <option name="myLimit" value="2678400000" />
+ </component>
+ <component name="XDebuggerManager">
+ <breakpoint-manager>
+ <breakpoints>
+ <line-breakpoint enabled="true" type="Dart">
+ <url>file://$PROJECT_DIR$/lib/src/flowcontrol/connection_queues.dart</url>
+ </line-breakpoint>
+ </breakpoints>
+ <option name="time" value="1" />
+ </breakpoint-manager>
+ </component>
+ <component name="editorHistoryManager">
+ <entry file="file://$PROJECT_DIR$/lib/transport.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="795">
+ <caret line="156" column="33" selection-start-line="156" selection-start-column="33" selection-end-line="156" selection-end-column="33" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/src/streams/stream_handler.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="2715">
+ <caret line="198" column="27" selection-start-line="198" selection-start-column="27" selection-end-line="198" selection-end-column="27" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/src/error_handler.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="960">
+ <caret line="68" column="17" selection-start-line="68" selection-start-column="17" selection-end-line="68" selection-end-column="17" />
+ <folding>
+ <element signature="e#251#271#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/src/flowcontrol/stream_queues.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="4020">
+ <caret line="280" column="7" selection-start-line="280" selection-start-column="7" selection-end-line="280" selection-end-column="7" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/src/flowcontrol/connection_queues.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="2895">
+ <caret line="210" column="15" selection-start-line="210" selection-start-column="15" selection-end-line="210" selection-end-column="15" />
+ <folding>
+ <element signature="e#410#430#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/src/connection.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="5820">
+ <caret line="405" column="61" selection-start-line="405" selection-start-column="61" selection-end-line="405" selection-end-column="61" />
+ <folding>
+ <element signature="e#0#17299#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/example/request_resource.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="90">
+ <caret line="6" column="22" lean-forward="true" selection-start-line="6" selection-start-column="22" selection-end-line="6" selection-end-column="22" />
+ <folding>
+ <element signature="e#0#20#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$USER_HOME$/install/dart-sdk/lib/io/secure_socket.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="150">
+ <caret line="12" column="15" selection-start-line="12" selection-start-column="15" selection-end-line="12" selection-end-column="15" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/example/minimal.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="495">
+ <caret line="33" column="26" selection-start-line="33" selection-start-column="26" selection-end-line="33" selection-end-column="26" />
+ <folding>
+ <element signature="e#0#22#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/example/display_headers.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="60">
+ <caret line="4" column="27" selection-start-line="4" selection-start-column="27" selection-end-line="4" selection-end-column="27" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/doc/api/index.html">
+ <provider selected="true" editor-type-id="text-editor" />
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/src/error_handler.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ <folding>
+ <element signature="e#251#271#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/src/connection_preface.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="60">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ <folding>
+ <element signature="e#0#3195#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/src/sync_errors.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="60">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/src/flowcontrol/connection_queues.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="8" selection-start-line="8" selection-end-line="8" />
+ <folding>
+ <element signature="e#410#430#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/src/flowcontrol/stream_queues.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/src/flowcontrol/window.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" column="14" lean-forward="true" selection-start-line="4" selection-start-column="14" selection-end-line="4" selection-end-column="14" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/src/flowcontrol/window_handler.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/src/frames/frame_defragmenter.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/src/frames/frames.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/src/hpack/hpack.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="15">
+ <caret line="7" selection-start-line="7" selection-end-line="7" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/src/hpack/huffman.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/src/hpack/huffman_table.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/src/settings/settings.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/src/streams/stream_handler.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/src/testing/debug.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/test/multiprotocol_server_test.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/test/server_test.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/test/src/connection_preface_test.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/test/src/error_matchers.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/test/src/streams/helper.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/test/src/streams/simple_flow_test.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/test/src/streams/simple_push_test.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/test/src/streams/streams_test.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/src/ping/ping_handler.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ <folding>
+ <element signature="e#217#237#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/src/artificial_server_socket.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ <folding>
+ <element signature="e#216#236#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/src/async_utils/async_utils.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ <folding>
+ <element signature="e#217#237#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/src/byte_utils.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/test/client_test.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ <folding>
+ <element signature="e#217#237#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/src/connection.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state>
+ <folding>
+ <element signature="e#0#17299#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/test/client_websites_test.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ <folding>
+ <element signature="e#217#237#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/src/testing/client.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ <folding>
+ <element signature="e#217#237#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/pubspec.yaml">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="150">
+ <caret line="10" column="17" selection-start-line="10" selection-start-column="17" selection-end-line="10" selection-end-column="17" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/test/transport_test.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="333">
+ <caret line="512" column="9" selection-start-line="512" selection-start-column="9" selection-end-line="512" selection-end-column="9" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/manual_test/out_of_stream_ids_test.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state>
+ <folding>
+ <element signature="e#0#2139#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/README.md">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="13">
+ <caret line="13" column="27" selection-start-line="13" selection-start-column="27" selection-end-line="13" selection-end-column="27" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/transport.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="15">
+ <caret line="1" column="12" selection-start-line="1" selection-start-column="12" selection-end-line="1" selection-end-column="12" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/multiprotocol_server.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="30">
+ <caret line="4" selection-start-line="4" selection-end-line="4" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/codereview.settings">
+ <provider selected="true" editor-type-id="text-editor" />
+ </entry>
+ <entry file="file://$PROJECT_DIR$/CHANGELOG.md">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="75">
+ <caret line="5" column="48" selection-start-line="5" selection-start-column="48" selection-end-line="5" selection-end-column="48" />
+ </state>
+ </provider>
+ </entry>
+ <entry file="file://$PROJECT_DIR$/lib/http2.dart">
+ <provider selected="true" editor-type-id="text-editor">
+ <state relative-caret-position="720">
+ <caret line="48" lean-forward="true" selection-start-line="48" selection-end-line="48" />
+ <folding>
+ <element signature="e#0#2418#0" expanded="true" />
+ <element signature="e#2364#2388#0" expanded="true" />
+ </folding>
+ </state>
+ </provider>
+ </entry>
+ </component>
+ <component name="masterDetails">
+ <states>
+ <state key="ProjectJDKs.UI">
+ <settings>
+ <splitter-proportions>
+ <option name="proportions">
+ <list>
+ <option value="0.2" />
+ </list>
+ </option>
+ </splitter-proportions>
+ </settings>
+ </state>
+ </states>
+ </component>
+</project>
\ No newline at end of file
diff --git a/http2/.test_config b/http2/.test_config
new file mode 100644
index 0000000..2fa4b96
--- /dev/null
+++ b/http2/.test_config
@@ -0,0 +1,5 @@
+{
+ "test_package": {
+ "platforms" : ["vm"]
+ }
+}
diff --git a/http2/.travis.yml b/http2/.travis.yml
new file mode 100644
index 0000000..9d69cdd
--- /dev/null
+++ b/http2/.travis.yml
@@ -0,0 +1,16 @@
+language: dart
+dart:
+ - dev
+
+dart_task:
+ - test
+ - dartfmt
+ - dartanalyzer
+
+# Only building master means that we don't run two builds for each pull request.
+branches:
+ only: [master]
+
+cache:
+ directories:
+ - $HOME/.pub-cache
diff --git a/http2/AUTHORS b/http2/AUTHORS
new file mode 100644
index 0000000..93b7228
--- /dev/null
+++ b/http2/AUTHORS
@@ -0,0 +1,8 @@
+# Below is a list of people and organizations that have contributed
+# to the project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google Inc. <*@google.com>
+
+Alexandre Ardhuin <alexandre.ardhuin@gmail.com>
diff --git a/http2/BUILD.gn b/http2/BUILD.gn
new file mode 100644
index 0000000..3e07fb2
--- /dev/null
+++ b/http2/BUILD.gn
@@ -0,0 +1,16 @@
+# This file is generated by importer.py for http2-1.0.0
+
+import("//build/dart/dart_library.gni")
+
+dart_library("http2") {
+ package_name = "http2"
+
+ # This parameter is left empty as we don't care about analysis or exporting
+ # these sources outside of the tree.
+ sources = []
+
+ disable_analysis = true
+
+ deps = [
+ ]
+}
diff --git a/http2/CHANGELOG.md b/http2/CHANGELOG.md
new file mode 100644
index 0000000..641a73b
--- /dev/null
+++ b/http2/CHANGELOG.md
@@ -0,0 +1,80 @@
+## 1.0
+
+* Graduate package to 1.0.
+* `package:http2/http2.dart` now reexports `package:http2/transport.dart`.
+
+## 0.1.9
+
+* Discard messages incoming after stream cancellation.
+
+## 0.1.8+2
+
+* On connection termination, try to dispatch existing messages, thereby avoiding
+ terminating existing streams.
+
+* Fix `ClientTransportConnection.isOpen` to return `false` if we have exhausted
+ the number of max-concurrent-streams.
+
+## 0.1.8+1
+
+* Switch all uppercase constants from `dart:convert` to lowercase.
+
+## 0.1.8
+
+* More changes required for making tests pass under Dart 2.0 runtime.
+* Modify sdk constraint to require '>=2.0.0-dev.40.0'.
+
+## 0.1.7
+
+* Fixes for Dart 2.0.
+
+## 0.1.6
+
+* Strong mode fixes and other cleanup.
+
+## 0.1.5
+
+* Removed use of new `Function` syntax, since it isn't fully supported in Dart
+ 1.24.
+
+## 0.1.4
+
+* Added an `onActiveStateChanged` callback to `Connection`, which is invoked when
+ the connection changes state from idle to active or from active to idle. This
+ can be used to implement an idle connection timeout.
+
+## 0.1.3
+
+* Fixed a bug where a closed window would not open correctly due to an increase
+ in initial window size.
+
+## 0.1.2
+
+* The endStream bit is now set on the requested frame, instead of on an empty
+ data frame following it.
+* Added an `onTerminated` hook that is called when a TransportStream receives
+ a RST_STREAM frame.
+
+## 0.1.1+2
+
+* Add errorCode to exception toString message.
+
+## 0.1.1+1
+
+* Fixing a performance issue in case the underlying socket is not writeable
+* Allow clients of MultiProtocolHttpServer to supply [http.ServerSettings]
+* Allow the draft version 'h2-14' in the ALPN protocol negogiation.
+
+## 0.1.1
+
+* Adding support for MultiProtocolHttpServer in the
+ `package:http2/multiprotocol_server.dart` library
+
+## 0.1.0
+
+* First version of a HTTP/2 transport implementation in the
+ `package:http2/transport.dart` library
+
+## 0.0.1
+
+- Initial version
diff --git a/http2/CONTRIBUTING.md b/http2/CONTRIBUTING.md
new file mode 100644
index 0000000..6f5e0ea
--- /dev/null
+++ b/http2/CONTRIBUTING.md
@@ -0,0 +1,33 @@
+Want to contribute? Great! First, read this page (including the small print at
+the end).
+
+### Before you contribute
+Before we can use your code, you must sign the
+[Google Individual Contributor License Agreement](https://cla.developers.google.com/about/google-individual)
+(CLA), which you can do online. The CLA is necessary mainly because you own the
+copyright to your changes, even after your contribution becomes part of our
+codebase, so we need your permission to use and distribute your code. We also
+need to be sure of various other things—for instance that you'll tell us if you
+know that your code infringes on other people's patents. You don't have to sign
+the CLA until after you've submitted your code for review and a member has
+approved it, but you must do it before we can put your code into our codebase.
+
+Before you start working on a larger contribution, you should get in touch with
+us first through the issue tracker with your idea so that we can help out and
+possibly guide you. Coordinating up front makes it much easier to avoid
+frustration later on.
+
+### Code reviews
+All submissions, including submissions by project members, require review.
+
+### File headers
+All files in the project must start with the following header.
+
+ // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+ // for details. All rights reserved. Use of this source code is governed by a
+ // BSD-style license that can be found in the LICENSE file.
+
+### The small print
+Contributions made by corporations are covered by a different agreement than the
+one above, the
+[Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate).
diff --git a/http2/LICENSE b/http2/LICENSE
new file mode 100644
index 0000000..de31e1a
--- /dev/null
+++ b/http2/LICENSE
@@ -0,0 +1,26 @@
+Copyright 2015, the Dart project authors. All rights reserved.
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+ * Neither the name of Google Inc. nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/http2/README.md b/http2/README.md
new file mode 100644
index 0000000..b910282
--- /dev/null
+++ b/http2/README.md
@@ -0,0 +1,62 @@
+# HTTP/2 for Dart
+
+This library provides an http/2 interface on top of a bidirectional stream of bytes.
+
+## Usage:
+
+Here is a minimal example of connecting to a http/2 capable server, requesting a resource and
+iterating over the response.
+
+```dart
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:http2/http2.dart';
+
+main() async {
+ var uri = Uri.parse('https://www.google.com/');
+
+ var transport = new ClientTransportConnection.viaSocket(
+ await SecureSocket.connect(
+ uri.host,
+ uri.port,
+ supportedProtocols: ['h2'],
+ ),
+ );
+
+ var stream = transport.makeRequest(
+ [
+ new Header.ascii(':method', 'GET'),
+ new Header.ascii(':path', uri.path),
+ new Header.ascii(':scheme', uri.scheme),
+ new Header.ascii(':authority', uri.host),
+ ],
+ endStream: true,
+ );
+
+ await for (var message in stream.incomingMessages) {
+ if (message is HeadersStreamMessage) {
+ for (var header in message.headers) {
+ var name = utf8.decode(header.name);
+ var value = utf8.decode(header.value);
+ print('Header: $name: $value');
+ }
+ } else if (message is DataStreamMessage) {
+ // Use [message.bytes] (but respect 'content-encoding' header)
+ }
+ }
+ await transport.finish();
+}
+```
+
+An example with better error handling is available [here][example].
+
+See the [API docs][api] for more details.
+
+## Features and bugs
+
+Please file feature requests and bugs at the [issue tracker][tracker].
+
+[tracker]: https://github.com/dart-lang/http2/issues
+[api]: http://www.dartdocs.org/documentation/http2/latest
+[example]: https://github.com/dart-lang/http2/blob/master/example/display_headers.dart.
diff --git a/http2/analysis_options.yaml b/http2/analysis_options.yaml
new file mode 100644
index 0000000..7244d25
--- /dev/null
+++ b/http2/analysis_options.yaml
@@ -0,0 +1,37 @@
+analyzer:
+ strong-mode: true
+ errors:
+ unused_element: error
+ unused_import: error
+ unused_local_variable: error
+ dead_code: error
+linter:
+ rules:
+ #- annotate_overrides
+ - avoid_empty_else
+ - avoid_init_to_null
+ - avoid_return_types_on_setters
+ - await_only_futures
+ - camel_case_types
+ - comment_references
+ - control_flow_in_finally
+ - directives_ordering
+ - empty_catches
+ - empty_constructor_bodies
+ - empty_statements
+ - hash_and_equals
+ - implementation_imports
+ - library_names
+ - library_prefixes
+ - non_constant_identifier_names
+ - only_throw_errors
+ - prefer_final_fields
+ - prefer_is_not_empty
+ #- prefer_single_quotes
+ - slash_for_doc_comments
+ - test_types_in_equals
+ - test_types_in_equals
+ - throw_in_finally
+ - type_init_formals
+ - unrelated_type_equality_checks
+ - valid_regexps
diff --git a/http2/codereview.settings b/http2/codereview.settings
new file mode 100644
index 0000000..69e23ea
--- /dev/null
+++ b/http2/codereview.settings
@@ -0,0 +1,3 @@
+CODE_REVIEW_SERVER: https://codereview.chromium.org/
+VIEW_VC: https://github.com/dart-lang/http2/commit/
+CC_LIST: reviews@dartlang.org
diff --git a/http2/doc/api/index.json b/http2/doc/api/index.json
new file mode 100644
index 0000000..2b4e5bf
--- /dev/null
+++ b/http2/doc/api/index.json
@@ -0,0 +1 @@
+[{"name":"http2.http2","qualifiedName":"http2.http2","href":"http2.http2/http2.http2-library.html","type":"library","overriddenDepth":0},{"name":"Header","qualifiedName":"http2.http2.Header","href":"http2.http2/Header-class.html","type":"class","overriddenDepth":0,"enclosedBy":{"name":"http2.http2","type":"library"}},{"name":"Header","qualifiedName":"http2.http2.Header","href":"http2.http2/Header/Header.html","type":"constructor","overriddenDepth":0,"enclosedBy":{"name":"Header","type":"class"}},{"name":"operator ==","qualifiedName":"http2.http2.Header.==","href":"http2.http2/Header/operator_equals.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"Header","type":"class"}},{"name":"Header.ascii","qualifiedName":"http2.http2.Header.ascii","href":"http2.http2/Header/Header.ascii.html","type":"constructor","overriddenDepth":0,"enclosedBy":{"name":"Header","type":"class"}},{"name":"hashCode","qualifiedName":"http2.http2.Header.hashCode","href":"http2.http2/Header/hashCode.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"Header","type":"class"}},{"name":"name","qualifiedName":"http2.http2.Header.name","href":"http2.http2/Header/name.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"Header","type":"class"}},{"name":"neverIndexed","qualifiedName":"http2.http2.Header.neverIndexed","href":"http2.http2/Header/neverIndexed.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"Header","type":"class"}},{"name":"noSuchMethod","qualifiedName":"http2.http2.Header.noSuchMethod","href":"http2.http2/Header/noSuchMethod.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"Header","type":"class"}},{"name":"runtimeType","qualifiedName":"http2.http2.Header.runtimeType","href":"http2.http2/Header/runtimeType.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"Header","type":"class"}},{"name":"toString","qualifiedName":"http2.http2.Header.toString","href":"http2.http2/Header/toString.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"Header","type":"class"}},{"name":"value","qualifiedName":"http2.http2.Header.value","href":"http2.http2/Header/value.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"Header","type":"class"}},{"name":"http2.multiprotocol_server","qualifiedName":"http2.multiprotocol_server","href":"http2.multiprotocol_server/http2.multiprotocol_server-library.html","type":"library","overriddenDepth":0},{"name":"MultiProtocolHttpServer","qualifiedName":"http2.multiprotocol_server.MultiProtocolHttpServer","href":"http2.multiprotocol_server/MultiProtocolHttpServer-class.html","type":"class","overriddenDepth":0,"enclosedBy":{"name":"http2.multiprotocol_server","type":"library"}},{"name":"operator ==","qualifiedName":"http2.multiprotocol_server.MultiProtocolHttpServer.==","href":"http2.multiprotocol_server/MultiProtocolHttpServer/operator_equals.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"MultiProtocolHttpServer","type":"class"}},{"name":"address","qualifiedName":"http2.multiprotocol_server.MultiProtocolHttpServer.address","href":"http2.multiprotocol_server/MultiProtocolHttpServer/address.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"MultiProtocolHttpServer","type":"class"}},{"name":"bind","qualifiedName":"http2.multiprotocol_server.MultiProtocolHttpServer.bind","href":"http2.multiprotocol_server/MultiProtocolHttpServer/bind.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"MultiProtocolHttpServer","type":"class"}},{"name":"close","qualifiedName":"http2.multiprotocol_server.MultiProtocolHttpServer.close","href":"http2.multiprotocol_server/MultiProtocolHttpServer/close.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"MultiProtocolHttpServer","type":"class"}},{"name":"hashCode","qualifiedName":"http2.multiprotocol_server.MultiProtocolHttpServer.hashCode","href":"http2.multiprotocol_server/MultiProtocolHttpServer/hashCode.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"MultiProtocolHttpServer","type":"class"}},{"name":"noSuchMethod","qualifiedName":"http2.multiprotocol_server.MultiProtocolHttpServer.noSuchMethod","href":"http2.multiprotocol_server/MultiProtocolHttpServer/noSuchMethod.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"MultiProtocolHttpServer","type":"class"}},{"name":"port","qualifiedName":"http2.multiprotocol_server.MultiProtocolHttpServer.port","href":"http2.multiprotocol_server/MultiProtocolHttpServer/port.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"MultiProtocolHttpServer","type":"class"}},{"name":"runtimeType","qualifiedName":"http2.multiprotocol_server.MultiProtocolHttpServer.runtimeType","href":"http2.multiprotocol_server/MultiProtocolHttpServer/runtimeType.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"MultiProtocolHttpServer","type":"class"}},{"name":"startServing","qualifiedName":"http2.multiprotocol_server.MultiProtocolHttpServer.startServing","href":"http2.multiprotocol_server/MultiProtocolHttpServer/startServing.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"MultiProtocolHttpServer","type":"class"}},{"name":"toString","qualifiedName":"http2.multiprotocol_server.MultiProtocolHttpServer.toString","href":"http2.multiprotocol_server/MultiProtocolHttpServer/toString.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"MultiProtocolHttpServer","type":"class"}},{"name":"transport","qualifiedName":"transport","href":"transport/transport-library.html","type":"library","overriddenDepth":0},{"name":"ActiveStateHandler","qualifiedName":"transport.ActiveStateHandler","href":"transport/ActiveStateHandler.html","type":"typedef","overriddenDepth":0,"enclosedBy":{"name":"transport","type":"library"}},{"name":"ClientSettings","qualifiedName":"transport.ClientSettings","href":"transport/ClientSettings-class.html","type":"class","overriddenDepth":0,"enclosedBy":{"name":"transport","type":"library"}},{"name":"ClientSettings","qualifiedName":"transport.ClientSettings","href":"transport/ClientSettings/ClientSettings.html","type":"constructor","overriddenDepth":0,"enclosedBy":{"name":"ClientSettings","type":"class"}},{"name":"allowServerPushes","qualifiedName":"transport.ClientSettings.allowServerPushes","href":"transport/ClientSettings/allowServerPushes.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"ClientSettings","type":"class"}},{"name":"ClientTransportConnection","qualifiedName":"transport.ClientTransportConnection","href":"transport/ClientTransportConnection-class.html","type":"class","overriddenDepth":0,"enclosedBy":{"name":"transport","type":"library"}},{"name":"isOpen","qualifiedName":"transport.ClientTransportConnection.isOpen","href":"transport/ClientTransportConnection/isOpen.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"ClientTransportConnection","type":"class"}},{"name":"makeRequest","qualifiedName":"transport.ClientTransportConnection.makeRequest","href":"transport/ClientTransportConnection/makeRequest.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"ClientTransportConnection","type":"class"}},{"name":"ClientTransportConnection.viaSocket","qualifiedName":"transport.ClientTransportConnection.viaSocket","href":"transport/ClientTransportConnection/ClientTransportConnection.viaSocket.html","type":"constructor","overriddenDepth":0,"enclosedBy":{"name":"ClientTransportConnection","type":"class"}},{"name":"ClientTransportConnection.viaStreams","qualifiedName":"transport.ClientTransportConnection.viaStreams","href":"transport/ClientTransportConnection/ClientTransportConnection.viaStreams.html","type":"constructor","overriddenDepth":0,"enclosedBy":{"name":"ClientTransportConnection","type":"class"}},{"name":"ClientTransportStream","qualifiedName":"transport.ClientTransportStream","href":"transport/ClientTransportStream-class.html","type":"class","overriddenDepth":0,"enclosedBy":{"name":"transport","type":"library"}},{"name":"ClientTransportStream","qualifiedName":"transport.ClientTransportStream","href":"transport/ClientTransportStream/ClientTransportStream.html","type":"constructor","overriddenDepth":0,"enclosedBy":{"name":"ClientTransportStream","type":"class"}},{"name":"peerPushes","qualifiedName":"transport.ClientTransportStream.peerPushes","href":"transport/ClientTransportStream/peerPushes.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"ClientTransportStream","type":"class"}},{"name":"DataStreamMessage","qualifiedName":"transport.DataStreamMessage","href":"transport/DataStreamMessage-class.html","type":"class","overriddenDepth":0,"enclosedBy":{"name":"transport","type":"library"}},{"name":"DataStreamMessage","qualifiedName":"transport.DataStreamMessage","href":"transport/DataStreamMessage/DataStreamMessage.html","type":"constructor","overriddenDepth":0,"enclosedBy":{"name":"DataStreamMessage","type":"class"}},{"name":"bytes","qualifiedName":"transport.DataStreamMessage.bytes","href":"transport/DataStreamMessage/bytes.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"DataStreamMessage","type":"class"}},{"name":"toString","qualifiedName":"transport.DataStreamMessage.toString","href":"transport/DataStreamMessage/toString.html","type":"method","overriddenDepth":1,"enclosedBy":{"name":"DataStreamMessage","type":"class"}},{"name":"HeadersStreamMessage","qualifiedName":"transport.HeadersStreamMessage","href":"transport/HeadersStreamMessage-class.html","type":"class","overriddenDepth":0,"enclosedBy":{"name":"transport","type":"library"}},{"name":"HeadersStreamMessage","qualifiedName":"transport.HeadersStreamMessage","href":"transport/HeadersStreamMessage/HeadersStreamMessage.html","type":"constructor","overriddenDepth":0,"enclosedBy":{"name":"HeadersStreamMessage","type":"class"}},{"name":"headers","qualifiedName":"transport.HeadersStreamMessage.headers","href":"transport/HeadersStreamMessage/headers.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"HeadersStreamMessage","type":"class"}},{"name":"toString","qualifiedName":"transport.HeadersStreamMessage.toString","href":"transport/HeadersStreamMessage/toString.html","type":"method","overriddenDepth":1,"enclosedBy":{"name":"HeadersStreamMessage","type":"class"}},{"name":"ServerSettings","qualifiedName":"transport.ServerSettings","href":"transport/ServerSettings-class.html","type":"class","overriddenDepth":0,"enclosedBy":{"name":"transport","type":"library"}},{"name":"ServerSettings","qualifiedName":"transport.ServerSettings","href":"transport/ServerSettings/ServerSettings.html","type":"constructor","overriddenDepth":0,"enclosedBy":{"name":"ServerSettings","type":"class"}},{"name":"ServerTransportConnection","qualifiedName":"transport.ServerTransportConnection","href":"transport/ServerTransportConnection-class.html","type":"class","overriddenDepth":0,"enclosedBy":{"name":"transport","type":"library"}},{"name":"incomingStreams","qualifiedName":"transport.ServerTransportConnection.incomingStreams","href":"transport/ServerTransportConnection/incomingStreams.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"ServerTransportConnection","type":"class"}},{"name":"ServerTransportConnection.viaSocket","qualifiedName":"transport.ServerTransportConnection.viaSocket","href":"transport/ServerTransportConnection/ServerTransportConnection.viaSocket.html","type":"constructor","overriddenDepth":0,"enclosedBy":{"name":"ServerTransportConnection","type":"class"}},{"name":"ServerTransportConnection.viaStreams","qualifiedName":"transport.ServerTransportConnection.viaStreams","href":"transport/ServerTransportConnection/ServerTransportConnection.viaStreams.html","type":"constructor","overriddenDepth":0,"enclosedBy":{"name":"ServerTransportConnection","type":"class"}},{"name":"ServerTransportStream","qualifiedName":"transport.ServerTransportStream","href":"transport/ServerTransportStream-class.html","type":"class","overriddenDepth":0,"enclosedBy":{"name":"transport","type":"library"}},{"name":"ServerTransportStream","qualifiedName":"transport.ServerTransportStream","href":"transport/ServerTransportStream/ServerTransportStream.html","type":"constructor","overriddenDepth":0,"enclosedBy":{"name":"ServerTransportStream","type":"class"}},{"name":"canPush","qualifiedName":"transport.ServerTransportStream.canPush","href":"transport/ServerTransportStream/canPush.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"ServerTransportStream","type":"class"}},{"name":"push","qualifiedName":"transport.ServerTransportStream.push","href":"transport/ServerTransportStream/push.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"ServerTransportStream","type":"class"}},{"name":"Settings","qualifiedName":"transport.Settings","href":"transport/Settings-class.html","type":"class","overriddenDepth":0,"enclosedBy":{"name":"transport","type":"library"}},{"name":"Settings","qualifiedName":"transport.Settings","href":"transport/Settings/Settings.html","type":"constructor","overriddenDepth":0,"enclosedBy":{"name":"Settings","type":"class"}},{"name":"operator ==","qualifiedName":"transport.Settings.==","href":"transport/Settings/operator_equals.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"Settings","type":"class"}},{"name":"concurrentStreamLimit","qualifiedName":"transport.Settings.concurrentStreamLimit","href":"transport/Settings/concurrentStreamLimit.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"Settings","type":"class"}},{"name":"hashCode","qualifiedName":"transport.Settings.hashCode","href":"transport/Settings/hashCode.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"Settings","type":"class"}},{"name":"noSuchMethod","qualifiedName":"transport.Settings.noSuchMethod","href":"transport/Settings/noSuchMethod.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"Settings","type":"class"}},{"name":"runtimeType","qualifiedName":"transport.Settings.runtimeType","href":"transport/Settings/runtimeType.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"Settings","type":"class"}},{"name":"streamWindowSize","qualifiedName":"transport.Settings.streamWindowSize","href":"transport/Settings/streamWindowSize.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"Settings","type":"class"}},{"name":"toString","qualifiedName":"transport.Settings.toString","href":"transport/Settings/toString.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"Settings","type":"class"}},{"name":"StreamMessage","qualifiedName":"transport.StreamMessage","href":"transport/StreamMessage-class.html","type":"class","overriddenDepth":0,"enclosedBy":{"name":"transport","type":"library"}},{"name":"StreamMessage","qualifiedName":"transport.StreamMessage","href":"transport/StreamMessage/StreamMessage.html","type":"constructor","overriddenDepth":0,"enclosedBy":{"name":"StreamMessage","type":"class"}},{"name":"operator ==","qualifiedName":"transport.StreamMessage.==","href":"transport/StreamMessage/operator_equals.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"StreamMessage","type":"class"}},{"name":"endStream","qualifiedName":"transport.StreamMessage.endStream","href":"transport/StreamMessage/endStream.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"StreamMessage","type":"class"}},{"name":"hashCode","qualifiedName":"transport.StreamMessage.hashCode","href":"transport/StreamMessage/hashCode.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"StreamMessage","type":"class"}},{"name":"noSuchMethod","qualifiedName":"transport.StreamMessage.noSuchMethod","href":"transport/StreamMessage/noSuchMethod.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"StreamMessage","type":"class"}},{"name":"runtimeType","qualifiedName":"transport.StreamMessage.runtimeType","href":"transport/StreamMessage/runtimeType.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"StreamMessage","type":"class"}},{"name":"toString","qualifiedName":"transport.StreamMessage.toString","href":"transport/StreamMessage/toString.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"StreamMessage","type":"class"}},{"name":"StreamTransportException","qualifiedName":"transport.StreamTransportException","href":"transport/StreamTransportException-class.html","type":"class","overriddenDepth":0,"enclosedBy":{"name":"transport","type":"library"}},{"name":"StreamTransportException","qualifiedName":"transport.StreamTransportException","href":"transport/StreamTransportException/StreamTransportException.html","type":"constructor","overriddenDepth":0,"enclosedBy":{"name":"StreamTransportException","type":"class"}},{"name":"TransportConnection","qualifiedName":"transport.TransportConnection","href":"transport/TransportConnection-class.html","type":"class","overriddenDepth":0,"enclosedBy":{"name":"transport","type":"library"}},{"name":"TransportConnection","qualifiedName":"transport.TransportConnection","href":"transport/TransportConnection/TransportConnection.html","type":"constructor","overriddenDepth":0,"enclosedBy":{"name":"TransportConnection","type":"class"}},{"name":"operator ==","qualifiedName":"transport.TransportConnection.==","href":"transport/TransportConnection/operator_equals.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"TransportConnection","type":"class"}},{"name":"finish","qualifiedName":"transport.TransportConnection.finish","href":"transport/TransportConnection/finish.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"TransportConnection","type":"class"}},{"name":"hashCode","qualifiedName":"transport.TransportConnection.hashCode","href":"transport/TransportConnection/hashCode.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"TransportConnection","type":"class"}},{"name":"noSuchMethod","qualifiedName":"transport.TransportConnection.noSuchMethod","href":"transport/TransportConnection/noSuchMethod.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"TransportConnection","type":"class"}},{"name":"onActiveStateChanged","qualifiedName":"transport.TransportConnection.onActiveStateChanged","href":"transport/TransportConnection/onActiveStateChanged.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"TransportConnection","type":"class"}},{"name":"ping","qualifiedName":"transport.TransportConnection.ping","href":"transport/TransportConnection/ping.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"TransportConnection","type":"class"}},{"name":"runtimeType","qualifiedName":"transport.TransportConnection.runtimeType","href":"transport/TransportConnection/runtimeType.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"TransportConnection","type":"class"}},{"name":"terminate","qualifiedName":"transport.TransportConnection.terminate","href":"transport/TransportConnection/terminate.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"TransportConnection","type":"class"}},{"name":"toString","qualifiedName":"transport.TransportConnection.toString","href":"transport/TransportConnection/toString.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"TransportConnection","type":"class"}},{"name":"TransportConnectionException","qualifiedName":"transport.TransportConnectionException","href":"transport/TransportConnectionException-class.html","type":"class","overriddenDepth":0,"enclosedBy":{"name":"transport","type":"library"}},{"name":"TransportConnectionException","qualifiedName":"transport.TransportConnectionException","href":"transport/TransportConnectionException/TransportConnectionException.html","type":"constructor","overriddenDepth":0,"enclosedBy":{"name":"TransportConnectionException","type":"class"}},{"name":"errorCode","qualifiedName":"transport.TransportConnectionException.errorCode","href":"transport/TransportConnectionException/errorCode.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"TransportConnectionException","type":"class"}},{"name":"TransportException","qualifiedName":"transport.TransportException","href":"transport/TransportException-class.html","type":"class","overriddenDepth":0,"enclosedBy":{"name":"transport","type":"library"}},{"name":"TransportException","qualifiedName":"transport.TransportException","href":"transport/TransportException/TransportException.html","type":"constructor","overriddenDepth":0,"enclosedBy":{"name":"TransportException","type":"class"}},{"name":"operator ==","qualifiedName":"transport.TransportException.==","href":"transport/TransportException/operator_equals.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"TransportException","type":"class"}},{"name":"hashCode","qualifiedName":"transport.TransportException.hashCode","href":"transport/TransportException/hashCode.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"TransportException","type":"class"}},{"name":"message","qualifiedName":"transport.TransportException.message","href":"transport/TransportException/message.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"TransportException","type":"class"}},{"name":"noSuchMethod","qualifiedName":"transport.TransportException.noSuchMethod","href":"transport/TransportException/noSuchMethod.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"TransportException","type":"class"}},{"name":"runtimeType","qualifiedName":"transport.TransportException.runtimeType","href":"transport/TransportException/runtimeType.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"TransportException","type":"class"}},{"name":"toString","qualifiedName":"transport.TransportException.toString","href":"transport/TransportException/toString.html","type":"method","overriddenDepth":1,"enclosedBy":{"name":"TransportException","type":"class"}},{"name":"TransportStream","qualifiedName":"transport.TransportStream","href":"transport/TransportStream-class.html","type":"class","overriddenDepth":0,"enclosedBy":{"name":"transport","type":"library"}},{"name":"TransportStream","qualifiedName":"transport.TransportStream","href":"transport/TransportStream/TransportStream.html","type":"constructor","overriddenDepth":0,"enclosedBy":{"name":"TransportStream","type":"class"}},{"name":"operator ==","qualifiedName":"transport.TransportStream.==","href":"transport/TransportStream/operator_equals.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"TransportStream","type":"class"}},{"name":"hashCode","qualifiedName":"transport.TransportStream.hashCode","href":"transport/TransportStream/hashCode.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"TransportStream","type":"class"}},{"name":"id","qualifiedName":"transport.TransportStream.id","href":"transport/TransportStream/id.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"TransportStream","type":"class"}},{"name":"incomingMessages","qualifiedName":"transport.TransportStream.incomingMessages","href":"transport/TransportStream/incomingMessages.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"TransportStream","type":"class"}},{"name":"noSuchMethod","qualifiedName":"transport.TransportStream.noSuchMethod","href":"transport/TransportStream/noSuchMethod.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"TransportStream","type":"class"}},{"name":"onTerminated","qualifiedName":"transport.TransportStream.onTerminated","href":"transport/TransportStream/onTerminated.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"TransportStream","type":"class"}},{"name":"outgoingMessages","qualifiedName":"transport.TransportStream.outgoingMessages","href":"transport/TransportStream/outgoingMessages.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"TransportStream","type":"class"}},{"name":"runtimeType","qualifiedName":"transport.TransportStream.runtimeType","href":"transport/TransportStream/runtimeType.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"TransportStream","type":"class"}},{"name":"sendData","qualifiedName":"transport.TransportStream.sendData","href":"transport/TransportStream/sendData.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"TransportStream","type":"class"}},{"name":"sendHeaders","qualifiedName":"transport.TransportStream.sendHeaders","href":"transport/TransportStream/sendHeaders.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"TransportStream","type":"class"}},{"name":"terminate","qualifiedName":"transport.TransportStream.terminate","href":"transport/TransportStream/terminate.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"TransportStream","type":"class"}},{"name":"toString","qualifiedName":"transport.TransportStream.toString","href":"transport/TransportStream/toString.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"TransportStream","type":"class"}},{"name":"TransportStreamPush","qualifiedName":"transport.TransportStreamPush","href":"transport/TransportStreamPush-class.html","type":"class","overriddenDepth":0,"enclosedBy":{"name":"transport","type":"library"}},{"name":"TransportStreamPush","qualifiedName":"transport.TransportStreamPush","href":"transport/TransportStreamPush/TransportStreamPush.html","type":"constructor","overriddenDepth":0,"enclosedBy":{"name":"TransportStreamPush","type":"class"}},{"name":"operator ==","qualifiedName":"transport.TransportStreamPush.==","href":"transport/TransportStreamPush/operator_equals.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"TransportStreamPush","type":"class"}},{"name":"hashCode","qualifiedName":"transport.TransportStreamPush.hashCode","href":"transport/TransportStreamPush/hashCode.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"TransportStreamPush","type":"class"}},{"name":"noSuchMethod","qualifiedName":"transport.TransportStreamPush.noSuchMethod","href":"transport/TransportStreamPush/noSuchMethod.html","type":"method","overriddenDepth":0,"enclosedBy":{"name":"TransportStreamPush","type":"class"}},{"name":"requestHeaders","qualifiedName":"transport.TransportStreamPush.requestHeaders","href":"transport/TransportStreamPush/requestHeaders.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"TransportStreamPush","type":"class"}},{"name":"runtimeType","qualifiedName":"transport.TransportStreamPush.runtimeType","href":"transport/TransportStreamPush/runtimeType.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"TransportStreamPush","type":"class"}},{"name":"stream","qualifiedName":"transport.TransportStreamPush.stream","href":"transport/TransportStreamPush/stream.html","type":"property","overriddenDepth":0,"enclosedBy":{"name":"TransportStreamPush","type":"class"}},{"name":"toString","qualifiedName":"transport.TransportStreamPush.toString","href":"transport/TransportStreamPush/toString.html","type":"method","overriddenDepth":1,"enclosedBy":{"name":"TransportStreamPush","type":"class"}}]
diff --git a/http2/doc/api/static-assets/css/bootstrap.css.map b/http2/doc/api/static-assets/css/bootstrap.css.map
new file mode 100644
index 0000000..2fd84f3
--- /dev/null
+++ b/http2/doc/api/static-assets/css/bootstrap.css.map
@@ -0,0 +1 @@
+{"version":3,"sources":["bootstrap.css","less/normalize.less","less/print.less","less/glyphicons.less","less/scaffolding.less","less/mixins/vendor-prefixes.less","less/mixins/tab-focus.less","less/mixins/image.less","less/type.less","less/mixins/text-emphasis.less","less/mixins/background-variant.less","less/mixins/text-overflow.less","less/code.less","less/grid.less","less/mixins/grid.less","less/mixins/grid-framework.less","less/tables.less","less/mixins/table-row.less","less/forms.less","less/mixins/forms.less","less/buttons.less","less/mixins/buttons.less","less/mixins/opacity.less","less/component-animations.less","less/dropdowns.less","less/mixins/nav-divider.less","less/mixins/reset-filter.less","less/button-groups.less","less/mixins/border-radius.less","less/input-groups.less","less/navs.less","less/navbar.less","less/mixins/nav-vertical-align.less","less/utilities.less","less/breadcrumbs.less","less/pagination.less","less/mixins/pagination.less","less/pager.less","less/labels.less","less/mixins/labels.less","less/badges.less","less/jumbotron.less","less/thumbnails.less","less/alerts.less","less/mixins/alerts.less","less/progress-bars.less","less/mixins/gradients.less","less/mixins/progress-bar.less","less/media.less","less/list-group.less","less/mixins/list-group.less","less/panels.less","less/mixins/panels.less","less/responsive-embed.less","less/wells.less","less/close.less","less/modals.less","less/tooltip.less","less/popovers.less","less/carousel.less","less/mixins/clearfix.less","less/mixins/center-block.less","less/mixins/hide-text.less","less/responsive-utilities.less","less/mixins/responsive-visibility.less"],"names":[],"mappings":"AAAA,6DAA4D;ACQ5D;EACE,yBAAA;EACA,4BAAA;EACA,gCAAA;EDND;ACaD;EACE,WAAA;EDXD;ACwBD;;;;;;;;;;;;;EAaE,gBAAA;EDtBD;AC8BD;;;;EAIE,uBAAA;EACA,0BAAA;ED5BD;ACoCD;EACE,eAAA;EACA,WAAA;EDlCD;AC0CD;;EAEE,eAAA;EDxCD;ACkDD;EACE,+BAAA;EDhDD;ACuDD;;EAEE,YAAA;EDrDD;AC+DD;EACE,2BAAA;ED7DD;ACoED;;EAEE,mBAAA;EDlED;ACyED;EACE,oBAAA;EDvED;AC+ED;EACE,gBAAA;EACA,kBAAA;ED7ED;ACoFD;EACE,kBAAA;EACA,aAAA;EDlFD;ACyFD;EACE,gBAAA;EDvFD;AC8FD;;EAEE,gBAAA;EACA,gBAAA;EACA,oBAAA;EACA,0BAAA;ED5FD;AC+FD;EACE,aAAA;ED7FD;ACgGD;EACE,iBAAA;ED9FD;ACwGD;EACE,WAAA;EDtGD;AC6GD;EACE,kBAAA;ED3GD;ACqHD;EACE,kBAAA;EDnHD;AC0HD;EACE,8BAAA;EACA,iCAAA;UAAA,yBAAA;EACA,WAAA;EDxHD;AC+HD;EACE,gBAAA;ED7HD;ACoID;;;;EAIE,mCAAA;EACA,gBAAA;EDlID;ACoJD;;;;;EAKE,gBAAA;EACA,eAAA;EACA,WAAA;EDlJD;ACyJD;EACE,mBAAA;EDvJD;ACiKD;;EAEE,sBAAA;ED/JD;AC0KD;;;;EAIE,4BAAA;EACA,iBAAA;EDxKD;AC+KD;;EAEE,iBAAA;ED7KD;ACoLD;;EAEE,WAAA;EACA,YAAA;EDlLD;AC0LD;EACE,qBAAA;EDxLD;ACmMD;;EAEE,gCAAA;KAAA,6BAAA;UAAA,wBAAA;EACA,YAAA;EDjMD;AC0MD;;EAEE,cAAA;EDxMD;ACiND;EACE,+BAAA;EACA,8BAAA;EACA,iCAAA;EACA,yBAAA;ED/MD;ACwND;;EAEE,0BAAA;EDtND;AC6ND;EACE,2BAAA;EACA,eAAA;EACA,gCAAA;ED3ND;ACmOD;EACE,WAAA;EACA,YAAA;EDjOD;ACwOD;EACE,gBAAA;EDtOD;AC8OD;EACE,mBAAA;ED5OD;ACsPD;EACE,2BAAA;EACA,mBAAA;EDpPD;ACuPD;;EAEE,YAAA;EDrPD;AACD,sFAAqF;AE1ErF;EAnGI;;;IAGI,oCAAA;IACA,wBAAA;IACA,qCAAA;YAAA,6BAAA;IACA,8BAAA;IFgLL;EE7KC;;IAEI,4BAAA;IF+KL;EE5KC;IACI,8BAAA;IF8KL;EE3KC;IACI,+BAAA;IF6KL;EExKC;;IAEI,aAAA;IF0KL;EEvKC;;IAEI,wBAAA;IACA,0BAAA;IFyKL;EEtKC;IACI,6BAAA;IFwKL;EErKC;;IAEI,0BAAA;IFuKL;EEpKC;IACI,4BAAA;IFsKL;EEnKC;;;IAGI,YAAA;IACA,WAAA;IFqKL;EElKC;;IAEI,yBAAA;IFoKL;EE7JC;IACI,6BAAA;IF+JL;EE3JC;IACI,eAAA;IF6JL;EE3JC;;IAGQ,mCAAA;IF4JT;EEzJC;IACI,wBAAA;IF2JL;EExJC;IACI,sCAAA;IF0JL;EE3JC;;IAKQ,mCAAA;IF0JT;EEvJC;;IAGQ,mCAAA;IFwJT;EACF;AGpPD;EACE,qCAAA;EACA,uDAAA;EACA,iYAAA;EHsPD;AG9OD;EACE,oBAAA;EACA,UAAA;EACA,uBAAA;EACA,qCAAA;EACA,oBAAA;EACA,qBAAA;EACA,gBAAA;EACA,qCAAA;EACA,oCAAA;EHgPD;AG5OmC;EAAW,gBAAA;EH+O9C;AG9OmC;EAAW,gBAAA;EHiP9C;AG/OmC;;EAAW,kBAAA;EHmP9C;AGlPmC;EAAW,kBAAA;EHqP9C;AGpPmC;EAAW,kBAAA;EHuP9C;AGtPmC;EAAW,kBAAA;EHyP9C;AGxPmC;EAAW,kBAAA;EH2P9C;AG1PmC;EAAW,kBAAA;EH6P9C;AG5PmC;EAAW,kBAAA;EH+P9C;AG9PmC;EAAW,kBAAA;EHiQ9C;AGhQmC;EAAW,kBAAA;EHmQ9C;AGlQmC;EAAW,kBAAA;EHqQ9C;AGpQmC;EAAW,kBAAA;EHuQ9C;AGtQmC;EAAW,kBAAA;EHyQ9C;AGxQmC;EAAW,kBAAA;EH2Q9C;AG1QmC;EAAW,kBAAA;EH6Q9C;AG5QmC;EAAW,kBAAA;EH+Q9C;AG9QmC;EAAW,kBAAA;EHiR9C;AGhRmC;EAAW,kBAAA;EHmR9C;AGlRmC;EAAW,kBAAA;EHqR9C;AGpRmC;EAAW,kBAAA;EHuR9C;AGtRmC;EAAW,kBAAA;EHyR9C;AGxRmC;EAAW,kBAAA;EH2R9C;AG1RmC;EAAW,kBAAA;EH6R9C;AG5RmC;EAAW,kBAAA;EH+R9C;AG9RmC;EAAW,kBAAA;EHiS9C;AGhSmC;EAAW,kBAAA;EHmS9C;AGlSmC;EAAW,kBAAA;EHqS9C;AGpSmC;EAAW,kBAAA;EHuS9C;AGtSmC;EAAW,kBAAA;EHyS9C;AGxSmC;EAAW,kBAAA;EH2S9C;AG1SmC;EAAW,kBAAA;EH6S9C;AG5SmC;EAAW,kBAAA;EH+S9C;AG9SmC;EAAW,kBAAA;EHiT9C;AGhTmC;EAAW,kBAAA;EHmT9C;AGlTmC;EAAW,kBAAA;EHqT9C;AGpTmC;EAAW,kBAAA;EHuT9C;AGtTmC;EAAW,kBAAA;EHyT9C;AGxTmC;EAAW,kBAAA;EH2T9C;AG1TmC;EAAW,kBAAA;EH6T9C;AG5TmC;EAAW,kBAAA;EH+T9C;AG9TmC;EAAW,kBAAA;EHiU9C;AGhUmC;EAAW,kBAAA;EHmU9C;AGlUmC;EAAW,kBAAA;EHqU9C;AGpUmC;EAAW,kBAAA;EHuU9C;AGtUmC;EAAW,kBAAA;EHyU9C;AGxUmC;EAAW,kBAAA;EH2U9C;AG1UmC;EAAW,kBAAA;EH6U9C;AG5UmC;EAAW,kBAAA;EH+U9C;AG9UmC;EAAW,kBAAA;EHiV9C;AGhVmC;EAAW,kBAAA;EHmV9C;AGlVmC;EAAW,kBAAA;EHqV9C;AGpVmC;EAAW,kBAAA;EHuV9C;AGtVmC;EAAW,kBAAA;EHyV9C;AGxVmC;EAAW,kBAAA;EH2V9C;AG1VmC;EAAW,kBAAA;EH6V9C;AG5VmC;EAAW,kBAAA;EH+V9C;AG9VmC;EAAW,kBAAA;EHiW9C;AGhWmC;EAAW,kBAAA;EHmW9C;AGlWmC;EAAW,kBAAA;EHqW9C;AGpWmC;EAAW,kBAAA;EHuW9C;AGtWmC;EAAW,kBAAA;EHyW9C;AGxWmC;EAAW,kBAAA;EH2W9C;AG1WmC;EAAW,kBAAA;EH6W9C;AG5WmC;EAAW,kBAAA;EH+W9C;AG9WmC;EAAW,kBAAA;EHiX9C;AGhXmC;EAAW,kBAAA;EHmX9C;AGlXmC;EAAW,kBAAA;EHqX9C;AGpXmC;EAAW,kBAAA;EHuX9C;AGtXmC;EAAW,kBAAA;EHyX9C;AGxXmC;EAAW,kBAAA;EH2X9C;AG1XmC;EAAW,kBAAA;EH6X9C;AG5XmC;EAAW,kBAAA;EH+X9C;AG9XmC;EAAW,kBAAA;EHiY9C;AGhYmC;EAAW,kBAAA;EHmY9C;AGlYmC;EAAW,kBAAA;EHqY9C;AGpYmC;EAAW,kBAAA;EHuY9C;AGtYmC;EAAW,kBAAA;EHyY9C;AGxYmC;EAAW,kBAAA;EH2Y9C;AG1YmC;EAAW,kBAAA;EH6Y9C;AG5YmC;EAAW,kBAAA;EH+Y9C;AG9YmC;EAAW,kBAAA;EHiZ9C;AGhZmC;EAAW,kBAAA;EHmZ9C;AGlZmC;EAAW,kBAAA;EHqZ9C;AGpZmC;EAAW,kBAAA;EHuZ9C;AGtZmC;EAAW,kBAAA;EHyZ9C;AGxZmC;EAAW,kBAAA;EH2Z9C;AG1ZmC;EAAW,kBAAA;EH6Z9C;AG5ZmC;EAAW,kBAAA;EH+Z9C;AG9ZmC;EAAW,kBAAA;EHia9C;AGhamC;EAAW,kBAAA;EHma9C;AGlamC;EAAW,kBAAA;EHqa9C;AGpamC;EAAW,kBAAA;EHua9C;AGtamC;EAAW,kBAAA;EHya9C;AGxamC;EAAW,kBAAA;EH2a9C;AG1amC;EAAW,kBAAA;EH6a9C;AG5amC;EAAW,kBAAA;EH+a9C;AG9amC;EAAW,kBAAA;EHib9C;AGhbmC;EAAW,kBAAA;EHmb9C;AGlbmC;EAAW,kBAAA;EHqb9C;AGpbmC;EAAW,kBAAA;EHub9C;AGtbmC;EAAW,kBAAA;EHyb9C;AGxbmC;EAAW,kBAAA;EH2b9C;AG1bmC;EAAW,kBAAA;EH6b9C;AG5bmC;EAAW,kBAAA;EH+b9C;AG9bmC;EAAW,kBAAA;EHic9C;AGhcmC;EAAW,kBAAA;EHmc9C;AGlcmC;EAAW,kBAAA;EHqc9C;AGpcmC;EAAW,kBAAA;EHuc9C;AGtcmC;EAAW,kBAAA;EHyc9C;AGxcmC;EAAW,kBAAA;EH2c9C;AG1cmC;EAAW,kBAAA;EH6c9C;AG5cmC;EAAW,kBAAA;EH+c9C;AG9cmC;EAAW,kBAAA;EHid9C;AGhdmC;EAAW,kBAAA;EHmd9C;AGldmC;EAAW,kBAAA;EHqd9C;AGpdmC;EAAW,kBAAA;EHud9C;AGtdmC;EAAW,kBAAA;EHyd9C;AGxdmC;EAAW,kBAAA;EH2d9C;AG1dmC;EAAW,kBAAA;EH6d9C;AG5dmC;EAAW,kBAAA;EH+d9C;AG9dmC;EAAW,kBAAA;EHie9C;AGhemC;EAAW,kBAAA;EHme9C;AGlemC;EAAW,kBAAA;EHqe9C;AGpemC;EAAW,kBAAA;EHue9C;AGtemC;EAAW,kBAAA;EHye9C;AGxemC;EAAW,kBAAA;EH2e9C;AG1emC;EAAW,kBAAA;EH6e9C;AG5emC;EAAW,kBAAA;EH+e9C;AG9emC;EAAW,kBAAA;EHif9C;AGhfmC;EAAW,kBAAA;EHmf9C;AGlfmC;EAAW,kBAAA;EHqf9C;AGpfmC;EAAW,kBAAA;EHuf9C;AGtfmC;EAAW,kBAAA;EHyf9C;AGxfmC;EAAW,kBAAA;EH2f9C;AG1fmC;EAAW,kBAAA;EH6f9C;AG5fmC;EAAW,kBAAA;EH+f9C;AG9fmC;EAAW,kBAAA;EHigB9C;AGhgBmC;EAAW,kBAAA;EHmgB9C;AGlgBmC;EAAW,kBAAA;EHqgB9C;AGpgBmC;EAAW,kBAAA;EHugB9C;AGtgBmC;EAAW,kBAAA;EHygB9C;AGxgBmC;EAAW,kBAAA;EH2gB9C;AG1gBmC;EAAW,kBAAA;EH6gB9C;AG5gBmC;EAAW,kBAAA;EH+gB9C;AG9gBmC;EAAW,kBAAA;EHihB9C;AGhhBmC;EAAW,kBAAA;EHmhB9C;AGlhBmC;EAAW,kBAAA;EHqhB9C;AGphBmC;EAAW,kBAAA;EHuhB9C;AGthBmC;EAAW,kBAAA;EHyhB9C;AGxhBmC;EAAW,kBAAA;EH2hB9C;AG1hBmC;EAAW,kBAAA;EH6hB9C;AG5hBmC;EAAW,kBAAA;EH+hB9C;AG9hBmC;EAAW,kBAAA;EHiiB9C;AGhiBmC;EAAW,kBAAA;EHmiB9C;AGliBmC;EAAW,kBAAA;EHqiB9C;AGpiBmC;EAAW,kBAAA;EHuiB9C;AGtiBmC;EAAW,kBAAA;EHyiB9C;AGxiBmC;EAAW,kBAAA;EH2iB9C;AG1iBmC;EAAW,kBAAA;EH6iB9C;AG5iBmC;EAAW,kBAAA;EH+iB9C;AG9iBmC;EAAW,kBAAA;EHijB9C;AGhjBmC;EAAW,kBAAA;EHmjB9C;AGljBmC;EAAW,kBAAA;EHqjB9C;AGpjBmC;EAAW,kBAAA;EHujB9C;AGtjBmC;EAAW,kBAAA;EHyjB9C;AGxjBmC;EAAW,kBAAA;EH2jB9C;AG1jBmC;EAAW,kBAAA;EH6jB9C;AG5jBmC;EAAW,kBAAA;EH+jB9C;AG9jBmC;EAAW,kBAAA;EHikB9C;AGhkBmC;EAAW,kBAAA;EHmkB9C;AGlkBmC;EAAW,kBAAA;EHqkB9C;AGpkBmC;EAAW,kBAAA;EHukB9C;AGtkBmC;EAAW,kBAAA;EHykB9C;AGxkBmC;EAAW,kBAAA;EH2kB9C;AG1kBmC;EAAW,kBAAA;EH6kB9C;AG5kBmC;EAAW,kBAAA;EH+kB9C;AG9kBmC;EAAW,kBAAA;EHilB9C;AGhlBmC;EAAW,kBAAA;EHmlB9C;AGllBmC;EAAW,kBAAA;EHqlB9C;AGplBmC;EAAW,kBAAA;EHulB9C;AGtlBmC;EAAW,kBAAA;EHylB9C;AGxlBmC;EAAW,kBAAA;EH2lB9C;AG1lBmC;EAAW,kBAAA;EH6lB9C;AG5lBmC;EAAW,kBAAA;EH+lB9C;AG9lBmC;EAAW,kBAAA;EHimB9C;AGhmBmC;EAAW,kBAAA;EHmmB9C;AGlmBmC;EAAW,kBAAA;EHqmB9C;AGpmBmC;EAAW,kBAAA;EHumB9C;AGtmBmC;EAAW,kBAAA;EHymB9C;AGxmBmC;EAAW,kBAAA;EH2mB9C;AG1mBmC;EAAW,kBAAA;EH6mB9C;AG5mBmC;EAAW,kBAAA;EH+mB9C;AG9mBmC;EAAW,kBAAA;EHinB9C;AGhnBmC;EAAW,kBAAA;EHmnB9C;AGlnBmC;EAAW,kBAAA;EHqnB9C;AGpnBmC;EAAW,kBAAA;EHunB9C;AGtnBmC;EAAW,kBAAA;EHynB9C;AGxnBmC;EAAW,kBAAA;EH2nB9C;AG1nBmC;EAAW,kBAAA;EH6nB9C;AG5nBmC;EAAW,kBAAA;EH+nB9C;AG9nBmC;EAAW,kBAAA;EHioB9C;AGhoBmC;EAAW,kBAAA;EHmoB9C;AGloBmC;EAAW,kBAAA;EHqoB9C;AGpoBmC;EAAW,kBAAA;EHuoB9C;AGtoBmC;EAAW,kBAAA;EHyoB9C;AGhoBmC;EAAW,kBAAA;EHmoB9C;AGloBmC;EAAW,kBAAA;EHqoB9C;AGpoBmC;EAAW,kBAAA;EHuoB9C;AGtoBmC;EAAW,kBAAA;EHyoB9C;AGxoBmC;EAAW,kBAAA;EH2oB9C;AG1oBmC;EAAW,kBAAA;EH6oB9C;AG5oBmC;EAAW,kBAAA;EH+oB9C;AG9oBmC;EAAW,kBAAA;EHipB9C;AGhpBmC;EAAW,kBAAA;EHmpB9C;AGlpBmC;EAAW,kBAAA;EHqpB9C;AGppBmC;EAAW,kBAAA;EHupB9C;AGtpBmC;EAAW,kBAAA;EHypB9C;AGxpBmC;EAAW,kBAAA;EH2pB9C;AG1pBmC;EAAW,kBAAA;EH6pB9C;AG5pBmC;EAAW,kBAAA;EH+pB9C;AG9pBmC;EAAW,kBAAA;EHiqB9C;AGhqBmC;EAAW,kBAAA;EHmqB9C;AGlqBmC;EAAW,kBAAA;EHqqB9C;AGpqBmC;EAAW,kBAAA;EHuqB9C;AGtqBmC;EAAW,kBAAA;EHyqB9C;AGxqBmC;EAAW,kBAAA;EH2qB9C;AG1qBmC;EAAW,kBAAA;EH6qB9C;AG5qBmC;EAAW,kBAAA;EH+qB9C;AG9qBmC;EAAW,kBAAA;EHirB9C;AGhrBmC;EAAW,kBAAA;EHmrB9C;AGlrBmC;EAAW,kBAAA;EHqrB9C;AGprBmC;EAAW,kBAAA;EHurB9C;AGtrBmC;EAAW,kBAAA;EHyrB9C;AGxrBmC;EAAW,kBAAA;EH2rB9C;AG1rBmC;EAAW,kBAAA;EH6rB9C;AG5rBmC;EAAW,kBAAA;EH+rB9C;AG9rBmC;EAAW,kBAAA;EHisB9C;AGhsBmC;EAAW,kBAAA;EHmsB9C;AGlsBmC;EAAW,kBAAA;EHqsB9C;AGpsBmC;EAAW,kBAAA;EHusB9C;AGtsBmC;EAAW,kBAAA;EHysB9C;AGxsBmC;EAAW,kBAAA;EH2sB9C;AG1sBmC;EAAW,kBAAA;EH6sB9C;AG5sBmC;EAAW,kBAAA;EH+sB9C;AG9sBmC;EAAW,kBAAA;EHitB9C;AGhtBmC;EAAW,kBAAA;EHmtB9C;AGltBmC;EAAW,kBAAA;EHqtB9C;AGptBmC;EAAW,kBAAA;EHutB9C;AGttBmC;EAAW,kBAAA;EHytB9C;AGxtBmC;EAAW,kBAAA;EH2tB9C;AG1tBmC;EAAW,kBAAA;EH6tB9C;AG5tBmC;EAAW,kBAAA;EH+tB9C;AG9tBmC;EAAW,kBAAA;EHiuB9C;AGhuBmC;EAAW,kBAAA;EHmuB9C;AGluBmC;EAAW,kBAAA;EHquB9C;AGpuBmC;EAAW,kBAAA;EHuuB9C;AGtuBmC;EAAW,kBAAA;EHyuB9C;AGxuBmC;EAAW,kBAAA;EH2uB9C;AG1uBmC;EAAW,kBAAA;EH6uB9C;AG5uBmC;EAAW,kBAAA;EH+uB9C;AG9uBmC;EAAW,kBAAA;EHivB9C;AIvhCD;ECgEE,gCAAA;EACG,6BAAA;EACK,wBAAA;EL09BT;AIzhCD;;EC6DE,gCAAA;EACG,6BAAA;EACK,wBAAA;ELg+BT;AIvhCD;EACE,iBAAA;EACA,+CAAA;EJyhCD;AIthCD;EACE,6DAAA;EACA,iBAAA;EACA,yBAAA;EACA,gBAAA;EACA,2BAAA;EJwhCD;AIphCD;;;;EAIE,sBAAA;EACA,oBAAA;EACA,sBAAA;EJshCD;AIhhCD;EACE,gBAAA;EACA,uBAAA;EJkhCD;AIhhCC;;EAEE,gBAAA;EACA,4BAAA;EJkhCH;AI/gCC;EErDA,sBAAA;EAEA,4CAAA;EACA,sBAAA;ENskCD;AIzgCD;EACE,WAAA;EJ2gCD;AIrgCD;EACE,wBAAA;EJugCD;AIngCD;;;;;EGvEE,gBAAA;EACA,iBAAA;EACA,cAAA;EPilCD;AIvgCD;EACE,oBAAA;EJygCD;AIngCD;EACE,cAAA;EACA,yBAAA;EACA,2BAAA;EACA,2BAAA;EACA,oBAAA;EC6FA,0CAAA;EACK,qCAAA;EACG,kCAAA;EEvLR,uBAAA;EACA,iBAAA;EACA,cAAA;EPimCD;AIngCD;EACE,oBAAA;EJqgCD;AI//BD;EACE,kBAAA;EACA,qBAAA;EACA,WAAA;EACA,+BAAA;EJigCD;AIz/BD;EACE,oBAAA;EACA,YAAA;EACA,aAAA;EACA,cAAA;EACA,YAAA;EACA,kBAAA;EACA,wBAAA;EACA,WAAA;EJ2/BD;AIn/BC;;EAEE,kBAAA;EACA,aAAA;EACA,cAAA;EACA,WAAA;EACA,mBAAA;EACA,YAAA;EJq/BH;AIz+BD;EACE,iBAAA;EJ2+BD;AQnoCD;;;;;;;;;;;;EAEE,sBAAA;EACA,kBAAA;EACA,kBAAA;EACA,gBAAA;ER+oCD;AQppCD;;;;;;;;;;;;;;;;;;;;;;;;EASI,qBAAA;EACA,gBAAA;EACA,gBAAA;ERqqCH;AQjqCD;;;;;;EAGE,kBAAA;EACA,qBAAA;ERsqCD;AQ1qCD;;;;;;;;;;;;EAQI,gBAAA;ERgrCH;AQ7qCD;;;;;;EAGE,kBAAA;EACA,qBAAA;ERkrCD;AQtrCD;;;;;;;;;;;;EAQI,gBAAA;ER4rCH;AQxrCD;;EAAU,iBAAA;ER4rCT;AQ3rCD;;EAAU,iBAAA;ER+rCT;AQ9rCD;;EAAU,iBAAA;ERksCT;AQjsCD;;EAAU,iBAAA;ERqsCT;AQpsCD;;EAAU,iBAAA;ERwsCT;AQvsCD;;EAAU,iBAAA;ER2sCT;AQrsCD;EACE,kBAAA;ERusCD;AQpsCD;EACE,qBAAA;EACA,iBAAA;EACA,kBAAA;EACA,kBAAA;ERssCD;AQjsCD;EAAA;IAFI,iBAAA;IRusCD;EACF;AQ/rCD;;EAEE,gBAAA;ERisCD;AQ9rCD;;EAEE,2BAAA;EACA,eAAA;ERgsCD;AQ5rCD;EAAuB,kBAAA;ER+rCtB;AQ9rCD;EAAuB,mBAAA;ERisCtB;AQhsCD;EAAuB,oBAAA;ERmsCtB;AQlsCD;EAAuB,qBAAA;ERqsCtB;AQpsCD;EAAuB,qBAAA;ERusCtB;AQpsCD;EAAuB,2BAAA;ERusCtB;AQtsCD;EAAuB,2BAAA;ERysCtB;AQxsCD;EAAuB,4BAAA;ER2sCtB;AQxsCD;EACE,gBAAA;ER0sCD;AQxsCD;ECrGE,gBAAA;ETgzCD;AS/yCC;EACE,gBAAA;ETizCH;AQ3sCD;ECxGE,gBAAA;ETszCD;ASrzCC;EACE,gBAAA;ETuzCH;AQ9sCD;EC3GE,gBAAA;ET4zCD;AS3zCC;EACE,gBAAA;ET6zCH;AQjtCD;EC9GE,gBAAA;ETk0CD;ASj0CC;EACE,gBAAA;ETm0CH;AQptCD;ECjHE,gBAAA;ETw0CD;ASv0CC;EACE,gBAAA;ETy0CH;AQntCD;EAGE,aAAA;EE3HA,2BAAA;EV+0CD;AU90CC;EACE,2BAAA;EVg1CH;AQptCD;EE9HE,2BAAA;EVq1CD;AUp1CC;EACE,2BAAA;EVs1CH;AQvtCD;EEjIE,2BAAA;EV21CD;AU11CC;EACE,2BAAA;EV41CH;AQ1tCD;EEpIE,2BAAA;EVi2CD;AUh2CC;EACE,2BAAA;EVk2CH;AQ7tCD;EEvIE,2BAAA;EVu2CD;AUt2CC;EACE,2BAAA;EVw2CH;AQ3tCD;EACE,qBAAA;EACA,qBAAA;EACA,kCAAA;ER6tCD;AQrtCD;;EAEE,eAAA;EACA,qBAAA;ERutCD;AQ1tCD;;;;EAMI,kBAAA;ER0tCH;AQntCD;EACE,iBAAA;EACA,kBAAA;ERqtCD;AQjtCD;EALE,iBAAA;EACA,kBAAA;EAMA,mBAAA;ERotCD;AQttCD;EAKI,uBAAA;EACA,mBAAA;EACA,oBAAA;ERotCH;AQ/sCD;EACE,eAAA;EACA,qBAAA;ERitCD;AQ/sCD;;EAEE,yBAAA;ERitCD;AQ/sCD;EACE,mBAAA;ERitCD;AQ/sCD;EACE,gBAAA;ERitCD;AQxrCD;EAAA;IAVM,aAAA;IACA,cAAA;IACA,aAAA;IACA,mBAAA;IGtNJ,kBAAA;IACA,yBAAA;IACA,qBAAA;IX65CC;EQlsCH;IAHM,oBAAA;IRwsCH;EACF;AQ/rCD;;EAGE,cAAA;EACA,mCAAA;ERgsCD;AQ9rCD;EACE,gBAAA;EA9IqB,2BAAA;ER+0CtB;AQ5rCD;EACE,oBAAA;EACA,kBAAA;EACA,mBAAA;EACA,gCAAA;ER8rCD;AQzrCG;;;EACE,kBAAA;ER6rCL;AQvsCD;;;EAmBI,gBAAA;EACA,gBAAA;EACA,yBAAA;EACA,gBAAA;ERyrCH;AQvrCG;;;EACE,wBAAA;ER2rCL;AQnrCD;;EAEE,qBAAA;EACA,iBAAA;EACA,iCAAA;EACA,gBAAA;EACA,mBAAA;ERqrCD;AQ/qCG;;;;;;EAAW,aAAA;ERurCd;AQtrCG;;;;;;EACE,wBAAA;ER6rCL;AQvrCD;EACE,qBAAA;EACA,oBAAA;EACA,yBAAA;ERyrCD;AY/9CD;;;;EAIE,gEAAA;EZi+CD;AY79CD;EACE,kBAAA;EACA,gBAAA;EACA,gBAAA;EACA,2BAAA;EACA,oBAAA;EZ+9CD;AY39CD;EACE,kBAAA;EACA,gBAAA;EACA,gBAAA;EACA,2BAAA;EACA,oBAAA;EACA,wDAAA;UAAA,gDAAA;EZ69CD;AYn+CD;EASI,YAAA;EACA,iBAAA;EACA,mBAAA;EACA,0BAAA;UAAA,kBAAA;EZ69CH;AYx9CD;EACE,gBAAA;EACA,gBAAA;EACA,kBAAA;EACA,iBAAA;EACA,yBAAA;EACA,uBAAA;EACA,uBAAA;EACA,gBAAA;EACA,2BAAA;EACA,2BAAA;EACA,oBAAA;EZ09CD;AYr+CD;EAeI,YAAA;EACA,oBAAA;EACA,gBAAA;EACA,uBAAA;EACA,+BAAA;EACA,kBAAA;EZy9CH;AYp9CD;EACE,mBAAA;EACA,oBAAA;EZs9CD;AahhDD;ECHE,oBAAA;EACA,mBAAA;EACA,oBAAA;EACA,qBAAA;EdshDD;AahhDC;EAAA;IAFE,cAAA;IbshDD;EACF;AalhDC;EAAA;IAFE,cAAA;IbwhDD;EACF;AaphDD;EAAA;IAFI,eAAA;Ib0hDD;EACF;AajhDD;ECvBE,oBAAA;EACA,mBAAA;EACA,oBAAA;EACA,qBAAA;Ed2iDD;Aa9gDD;ECvBE,oBAAA;EACA,qBAAA;EdwiDD;AexiDG;EACE,oBAAA;EAEA,iBAAA;EAEA,oBAAA;EACA,qBAAA;EfwiDL;AexhDG;EACE,aAAA;Ef0hDL;AenhDC;EACE,aAAA;EfqhDH;AethDC;EACE,qBAAA;EfwhDH;AezhDC;EACE,qBAAA;Ef2hDH;Ae5hDC;EACE,YAAA;Ef8hDH;Ae/hDC;EACE,qBAAA;EfiiDH;AeliDC;EACE,qBAAA;EfoiDH;AeriDC;EACE,YAAA;EfuiDH;AexiDC;EACE,qBAAA;Ef0iDH;Ae3iDC;EACE,qBAAA;Ef6iDH;Ae9iDC;EACE,YAAA;EfgjDH;AejjDC;EACE,qBAAA;EfmjDH;AepjDC;EACE,oBAAA;EfsjDH;AexiDC;EACE,aAAA;Ef0iDH;Ae3iDC;EACE,qBAAA;Ef6iDH;Ae9iDC;EACE,qBAAA;EfgjDH;AejjDC;EACE,YAAA;EfmjDH;AepjDC;EACE,qBAAA;EfsjDH;AevjDC;EACE,qBAAA;EfyjDH;Ae1jDC;EACE,YAAA;Ef4jDH;Ae7jDC;EACE,qBAAA;Ef+jDH;AehkDC;EACE,qBAAA;EfkkDH;AenkDC;EACE,YAAA;EfqkDH;AetkDC;EACE,qBAAA;EfwkDH;AezkDC;EACE,oBAAA;Ef2kDH;AevkDC;EACE,aAAA;EfykDH;AezlDC;EACE,YAAA;Ef2lDH;Ae5lDC;EACE,oBAAA;Ef8lDH;Ae/lDC;EACE,oBAAA;EfimDH;AelmDC;EACE,WAAA;EfomDH;AermDC;EACE,oBAAA;EfumDH;AexmDC;EACE,oBAAA;Ef0mDH;Ae3mDC;EACE,WAAA;Ef6mDH;Ae9mDC;EACE,oBAAA;EfgnDH;AejnDC;EACE,oBAAA;EfmnDH;AepnDC;EACE,WAAA;EfsnDH;AevnDC;EACE,oBAAA;EfynDH;Ae1nDC;EACE,mBAAA;Ef4nDH;AexnDC;EACE,YAAA;Ef0nDH;Ae5mDC;EACE,mBAAA;Ef8mDH;Ae/mDC;EACE,2BAAA;EfinDH;AelnDC;EACE,2BAAA;EfonDH;AernDC;EACE,kBAAA;EfunDH;AexnDC;EACE,2BAAA;Ef0nDH;Ae3nDC;EACE,2BAAA;Ef6nDH;Ae9nDC;EACE,kBAAA;EfgoDH;AejoDC;EACE,2BAAA;EfmoDH;AepoDC;EACE,2BAAA;EfsoDH;AevoDC;EACE,kBAAA;EfyoDH;Ae1oDC;EACE,2BAAA;Ef4oDH;Ae7oDC;EACE,0BAAA;Ef+oDH;AehpDC;EACE,iBAAA;EfkpDH;AalpDD;EElCI;IACE,aAAA;IfurDH;EehrDD;IACE,aAAA;IfkrDD;EenrDD;IACE,qBAAA;IfqrDD;EetrDD;IACE,qBAAA;IfwrDD;EezrDD;IACE,YAAA;If2rDD;Ee5rDD;IACE,qBAAA;If8rDD;Ee/rDD;IACE,qBAAA;IfisDD;EelsDD;IACE,YAAA;IfosDD;EersDD;IACE,qBAAA;IfusDD;EexsDD;IACE,qBAAA;If0sDD;Ee3sDD;IACE,YAAA;If6sDD;Ee9sDD;IACE,qBAAA;IfgtDD;EejtDD;IACE,oBAAA;IfmtDD;EersDD;IACE,aAAA;IfusDD;EexsDD;IACE,qBAAA;If0sDD;Ee3sDD;IACE,qBAAA;If6sDD;Ee9sDD;IACE,YAAA;IfgtDD;EejtDD;IACE,qBAAA;IfmtDD;EeptDD;IACE,qBAAA;IfstDD;EevtDD;IACE,YAAA;IfytDD;Ee1tDD;IACE,qBAAA;If4tDD;Ee7tDD;IACE,qBAAA;If+tDD;EehuDD;IACE,YAAA;IfkuDD;EenuDD;IACE,qBAAA;IfquDD;EetuDD;IACE,oBAAA;IfwuDD;EepuDD;IACE,aAAA;IfsuDD;EetvDD;IACE,YAAA;IfwvDD;EezvDD;IACE,oBAAA;If2vDD;Ee5vDD;IACE,oBAAA;If8vDD;Ee/vDD;IACE,WAAA;IfiwDD;EelwDD;IACE,oBAAA;IfowDD;EerwDD;IACE,oBAAA;IfuwDD;EexwDD;IACE,WAAA;If0wDD;Ee3wDD;IACE,oBAAA;If6wDD;Ee9wDD;IACE,oBAAA;IfgxDD;EejxDD;IACE,WAAA;IfmxDD;EepxDD;IACE,oBAAA;IfsxDD;EevxDD;IACE,mBAAA;IfyxDD;EerxDD;IACE,YAAA;IfuxDD;EezwDD;IACE,mBAAA;If2wDD;Ee5wDD;IACE,2BAAA;If8wDD;Ee/wDD;IACE,2BAAA;IfixDD;EelxDD;IACE,kBAAA;IfoxDD;EerxDD;IACE,2BAAA;IfuxDD;EexxDD;IACE,2BAAA;If0xDD;Ee3xDD;IACE,kBAAA;If6xDD;Ee9xDD;IACE,2BAAA;IfgyDD;EejyDD;IACE,2BAAA;IfmyDD;EepyDD;IACE,kBAAA;IfsyDD;EevyDD;IACE,2BAAA;IfyyDD;Ee1yDD;IACE,0BAAA;If4yDD;Ee7yDD;IACE,iBAAA;If+yDD;EACF;AavyDD;EE3CI;IACE,aAAA;Ifq1DH;Ee90DD;IACE,aAAA;Ifg1DD;Eej1DD;IACE,qBAAA;Ifm1DD;Eep1DD;IACE,qBAAA;Ifs1DD;Eev1DD;IACE,YAAA;Ify1DD;Ee11DD;IACE,qBAAA;If41DD;Ee71DD;IACE,qBAAA;If+1DD;Eeh2DD;IACE,YAAA;Ifk2DD;Een2DD;IACE,qBAAA;Ifq2DD;Eet2DD;IACE,qBAAA;Ifw2DD;Eez2DD;IACE,YAAA;If22DD;Ee52DD;IACE,qBAAA;If82DD;Ee/2DD;IACE,oBAAA;Ifi3DD;Een2DD;IACE,aAAA;Ifq2DD;Eet2DD;IACE,qBAAA;Ifw2DD;Eez2DD;IACE,qBAAA;If22DD;Ee52DD;IACE,YAAA;If82DD;Ee/2DD;IACE,qBAAA;Ifi3DD;Eel3DD;IACE,qBAAA;Ifo3DD;Eer3DD;IACE,YAAA;Ifu3DD;Eex3DD;IACE,qBAAA;If03DD;Ee33DD;IACE,qBAAA;If63DD;Ee93DD;IACE,YAAA;Ifg4DD;Eej4DD;IACE,qBAAA;Ifm4DD;Eep4DD;IACE,oBAAA;Ifs4DD;Eel4DD;IACE,aAAA;Ifo4DD;Eep5DD;IACE,YAAA;Ifs5DD;Eev5DD;IACE,oBAAA;Ify5DD;Ee15DD;IACE,oBAAA;If45DD;Ee75DD;IACE,WAAA;If+5DD;Eeh6DD;IACE,oBAAA;Ifk6DD;Een6DD;IACE,oBAAA;Ifq6DD;Eet6DD;IACE,WAAA;Ifw6DD;Eez6DD;IACE,oBAAA;If26DD;Ee56DD;IACE,oBAAA;If86DD;Ee/6DD;IACE,WAAA;Ifi7DD;Eel7DD;IACE,oBAAA;Ifo7DD;Eer7DD;IACE,mBAAA;Ifu7DD;Een7DD;IACE,YAAA;Ifq7DD;Eev6DD;IACE,mBAAA;Ify6DD;Ee16DD;IACE,2BAAA;If46DD;Ee76DD;IACE,2BAAA;If+6DD;Eeh7DD;IACE,kBAAA;Ifk7DD;Een7DD;IACE,2BAAA;Ifq7DD;Eet7DD;IACE,2BAAA;Ifw7DD;Eez7DD;IACE,kBAAA;If27DD;Ee57DD;IACE,2BAAA;If87DD;Ee/7DD;IACE,2BAAA;Ifi8DD;Eel8DD;IACE,kBAAA;Ifo8DD;Eer8DD;IACE,2BAAA;Ifu8DD;Eex8DD;IACE,0BAAA;If08DD;Ee38DD;IACE,iBAAA;If68DD;EACF;Aal8DD;EE9CI;IACE,aAAA;Ifm/DH;Ee5+DD;IACE,aAAA;If8+DD;Ee/+DD;IACE,qBAAA;Ifi/DD;Eel/DD;IACE,qBAAA;Ifo/DD;Eer/DD;IACE,YAAA;Ifu/DD;Eex/DD;IACE,qBAAA;If0/DD;Ee3/DD;IACE,qBAAA;If6/DD;Ee9/DD;IACE,YAAA;IfggED;EejgED;IACE,qBAAA;IfmgED;EepgED;IACE,qBAAA;IfsgED;EevgED;IACE,YAAA;IfygED;Ee1gED;IACE,qBAAA;If4gED;Ee7gED;IACE,oBAAA;If+gED;EejgED;IACE,aAAA;IfmgED;EepgED;IACE,qBAAA;IfsgED;EevgED;IACE,qBAAA;IfygED;Ee1gED;IACE,YAAA;If4gED;Ee7gED;IACE,qBAAA;If+gED;EehhED;IACE,qBAAA;IfkhED;EenhED;IACE,YAAA;IfqhED;EethED;IACE,qBAAA;IfwhED;EezhED;IACE,qBAAA;If2hED;Ee5hED;IACE,YAAA;If8hED;Ee/hED;IACE,qBAAA;IfiiED;EeliED;IACE,oBAAA;IfoiED;EehiED;IACE,aAAA;IfkiED;EeljED;IACE,YAAA;IfojED;EerjED;IACE,oBAAA;IfujED;EexjED;IACE,oBAAA;If0jED;Ee3jED;IACE,WAAA;If6jED;Ee9jED;IACE,oBAAA;IfgkED;EejkED;IACE,oBAAA;IfmkED;EepkED;IACE,WAAA;IfskED;EevkED;IACE,oBAAA;IfykED;Ee1kED;IACE,oBAAA;If4kED;Ee7kED;IACE,WAAA;If+kED;EehlED;IACE,oBAAA;IfklED;EenlED;IACE,mBAAA;IfqlED;EejlED;IACE,YAAA;IfmlED;EerkED;IACE,mBAAA;IfukED;EexkED;IACE,2BAAA;If0kED;Ee3kED;IACE,2BAAA;If6kED;Ee9kED;IACE,kBAAA;IfglED;EejlED;IACE,2BAAA;IfmlED;EeplED;IACE,2BAAA;IfslED;EevlED;IACE,kBAAA;IfylED;Ee1lED;IACE,2BAAA;If4lED;Ee7lED;IACE,2BAAA;If+lED;EehmED;IACE,kBAAA;IfkmED;EenmED;IACE,2BAAA;IfqmED;EetmED;IACE,0BAAA;IfwmED;EezmED;IACE,iBAAA;If2mED;EACF;AgB/qED;EACE,+BAAA;EhBirED;AgB/qED;EACE,kBAAA;EACA,qBAAA;EACA,gBAAA;EACA,kBAAA;EhBirED;AgB/qED;EACE,kBAAA;EhBirED;AgB3qED;EACE,aAAA;EACA,iBAAA;EACA,qBAAA;EhB6qED;AgBhrED;;;;;;EAWQ,cAAA;EACA,yBAAA;EACA,qBAAA;EACA,+BAAA;EhB6qEP;AgB3rED;EAoBI,wBAAA;EACA,kCAAA;EhB0qEH;AgB/rED;;;;;;EA8BQ,eAAA;EhByqEP;AgBvsED;EAoCI,+BAAA;EhBsqEH;AgB1sED;EAyCI,2BAAA;EhBoqEH;AgB7pED;;;;;;EAOQ,cAAA;EhB8pEP;AgBnpED;EACE,2BAAA;EhBqpED;AgBtpED;;;;;;EAQQ,2BAAA;EhBspEP;AgB9pED;;EAeM,0BAAA;EhBmpEL;AgBzoED;EAEI,2BAAA;EhB0oEH;AgBjoED;EAEI,2BAAA;EhBkoEH;AgBznED;EACE,kBAAA;EACA,aAAA;EACA,uBAAA;EhB2nED;AgBtnEG;;EACE,kBAAA;EACA,aAAA;EACA,qBAAA;EhBynEL;AiBrwEC;;;;;;;;;;;;EAOI,2BAAA;EjB4wEL;AiBtwEC;;;;;EAMI,2BAAA;EjBuwEL;AiB1xEC;;;;;;;;;;;;EAOI,2BAAA;EjBiyEL;AiB3xEC;;;;;EAMI,2BAAA;EjB4xEL;AiB/yEC;;;;;;;;;;;;EAOI,2BAAA;EjBszEL;AiBhzEC;;;;;EAMI,2BAAA;EjBizEL;AiBp0EC;;;;;;;;;;;;EAOI,2BAAA;EjB20EL;AiBr0EC;;;;;EAMI,2BAAA;EjBs0EL;AiBz1EC;;;;;;;;;;;;EAOI,2BAAA;EjBg2EL;AiB11EC;;;;;EAMI,2BAAA;EjB21EL;AgBzsED;EACE,kBAAA;EACA,mBAAA;EhB2sED;AgB9oED;EAAA;IA1DI,aAAA;IACA,qBAAA;IACA,oBAAA;IACA,8CAAA;IACA,2BAAA;IhB4sED;EgBtpEH;IAlDM,kBAAA;IhB2sEH;EgBzpEH;;;;;;IAzCY,qBAAA;IhB0sET;EgBjqEH;IAjCM,WAAA;IhBqsEH;EgBpqEH;;;;;;IAxBY,gBAAA;IhBosET;EgB5qEH;;;;;;IApBY,iBAAA;IhBwsET;EgBprEH;;;;IAPY,kBAAA;IhBisET;EACF;AkB35ED;EACE,YAAA;EACA,WAAA;EACA,WAAA;EAIA,cAAA;ElB05ED;AkBv5ED;EACE,gBAAA;EACA,aAAA;EACA,YAAA;EACA,qBAAA;EACA,iBAAA;EACA,sBAAA;EACA,gBAAA;EACA,WAAA;EACA,kCAAA;ElBy5ED;AkBt5ED;EACE,uBAAA;EACA,iBAAA;EACA,oBAAA;EACA,mBAAA;ElBw5ED;AkB74ED;Eb4BE,gCAAA;EACG,6BAAA;EACK,wBAAA;ELo3ET;AkB74ED;;EAEE,iBAAA;EACA,oBAAA;EACA,qBAAA;ElB+4ED;AkB34ED;EACE,gBAAA;ElB64ED;AkBz4ED;EACE,gBAAA;EACA,aAAA;ElB24ED;AkBv4ED;;EAEE,cAAA;ElBy4ED;AkBr4ED;;;EZxEE,sBAAA;EAEA,4CAAA;EACA,sBAAA;ENi9ED;AkBr4ED;EACE,gBAAA;EACA,kBAAA;EACA,iBAAA;EACA,yBAAA;EACA,gBAAA;ElBu4ED;AkB72ED;EACE,gBAAA;EACA,aAAA;EACA,cAAA;EACA,mBAAA;EACA,iBAAA;EACA,yBAAA;EACA,gBAAA;EACA,2BAAA;EACA,wBAAA;EACA,2BAAA;EACA,oBAAA;EbzDA,0DAAA;EACQ,kDAAA;EAyHR,wFAAA;EACK,2EAAA;EACG,wEAAA;ELizET;AmBz7EC;EACE,uBAAA;EACA,YAAA;EdUF,wFAAA;EACQ,gFAAA;ELk7ET;AKj5EC;EACE,gBAAA;EACA,YAAA;ELm5EH;AKj5EC;EAA0B,gBAAA;ELo5E3B;AKn5EC;EAAgC,gBAAA;ELs5EjC;AkBr3EC;;;EAGE,2BAAA;EACA,YAAA;ElBu3EH;AkBp3EC;;EAEE,qBAAA;ElBs3EH;AkBl3EC;EACE,cAAA;ElBo3EH;AkBx2ED;EACE,0BAAA;ElB02ED;AkBt0ED;EAxBE;;;;IAIE,mBAAA;IlBi2ED;EkB/1EC;;;;;;;;IAEE,mBAAA;IlBu2EH;EkBp2EC;;;;;;;;IAEE,mBAAA;IlB42EH;EACF;AkBl2ED;EACE,qBAAA;ElBo2ED;AkB51ED;;EAEE,oBAAA;EACA,gBAAA;EACA,kBAAA;EACA,qBAAA;ElB81ED;AkBn2ED;;EAQI,kBAAA;EACA,oBAAA;EACA,kBAAA;EACA,qBAAA;EACA,iBAAA;ElB+1EH;AkB51ED;;;;EAIE,oBAAA;EACA,oBAAA;EACA,oBAAA;ElB81ED;AkB31ED;;EAEE,kBAAA;ElB61ED;AkBz1ED;;EAEE,oBAAA;EACA,uBAAA;EACA,oBAAA;EACA,kBAAA;EACA,wBAAA;EACA,qBAAA;EACA,iBAAA;ElB21ED;AkBz1ED;;EAEE,eAAA;EACA,mBAAA;ElB21ED;AkBl1EC;;;;;;EAGE,qBAAA;ElBu1EH;AkBj1EC;;;;EAEE,qBAAA;ElBq1EH;AkB/0EC;;;;EAGI,qBAAA;ElBk1EL;AkBv0ED;EAEE,kBAAA;EACA,qBAAA;EAEA,kBAAA;EACA,kBAAA;ElBu0ED;AkBr0EC;;EAEE,iBAAA;EACA,kBAAA;ElBu0EH;AkB1zED;EC1PE,cAAA;EACA,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,oBAAA;EnBujFD;AmBrjFC;EACE,cAAA;EACA,mBAAA;EnBujFH;AmBpjFC;;EAEE,cAAA;EnBsjFH;AkBt0ED;EC7PE,cAAA;EACA,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,oBAAA;EnBskFD;AmBpkFC;EACE,cAAA;EACA,mBAAA;EnBskFH;AmBnkFC;;EAEE,cAAA;EnBqkFH;AkBr1ED;EAKI,cAAA;EACA,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,kBAAA;ElBm1EH;AkB/0ED;EC1QE,cAAA;EACA,oBAAA;EACA,iBAAA;EACA,wBAAA;EACA,oBAAA;EnB4lFD;AmB1lFC;EACE,cAAA;EACA,mBAAA;EnB4lFH;AmBzlFC;;EAEE,cAAA;EnB2lFH;AkB31ED;EC7QE,cAAA;EACA,oBAAA;EACA,iBAAA;EACA,wBAAA;EACA,oBAAA;EnB2mFD;AmBzmFC;EACE,cAAA;EACA,mBAAA;EnB2mFH;AmBxmFC;;EAEE,cAAA;EnB0mFH;AkB12ED;EAKI,cAAA;EACA,oBAAA;EACA,iBAAA;EACA,wBAAA;EACA,kBAAA;ElBw2EH;AkB/1ED;EAEE,oBAAA;ElBg2ED;AkBl2ED;EAMI,uBAAA;ElB+1EH;AkB31ED;EACE,oBAAA;EACA,QAAA;EACA,UAAA;EACA,YAAA;EACA,gBAAA;EACA,aAAA;EACA,cAAA;EACA,mBAAA;EACA,oBAAA;EACA,sBAAA;ElB61ED;AkB31ED;EACE,aAAA;EACA,cAAA;EACA,mBAAA;ElB61ED;AkB31ED;EACE,aAAA;EACA,cAAA;EACA,mBAAA;ElB61ED;AkBz1ED;;;;;;;;;;ECrXI,gBAAA;EnB0tFH;AkBr2ED;ECjXI,uBAAA;Ed+CF,0DAAA;EACQ,kDAAA;EL2qFT;AmBztFG;EACE,uBAAA;Ed4CJ,2EAAA;EACQ,mEAAA;ELgrFT;AkB/2ED;ECvWI,gBAAA;EACA,uBAAA;EACA,2BAAA;EnBytFH;AkBp3ED;ECjWI,gBAAA;EnBwtFH;AkBp3ED;;;;;;;;;;ECxXI,gBAAA;EnBwvFH;AkBh4ED;ECpXI,uBAAA;Ed+CF,0DAAA;EACQ,kDAAA;ELysFT;AmBvvFG;EACE,uBAAA;Ed4CJ,2EAAA;EACQ,mEAAA;EL8sFT;AkB14ED;EC1WI,gBAAA;EACA,uBAAA;EACA,2BAAA;EnBuvFH;AkB/4ED;ECpWI,gBAAA;EnBsvFH;AkB/4ED;;;;;;;;;;EC3XI,gBAAA;EnBsxFH;AkB35ED;ECvXI,uBAAA;Ed+CF,0DAAA;EACQ,kDAAA;ELuuFT;AmBrxFG;EACE,uBAAA;Ed4CJ,2EAAA;EACQ,mEAAA;EL4uFT;AkBr6ED;EC7WI,gBAAA;EACA,uBAAA;EACA,2BAAA;EnBqxFH;AkB16ED;ECvWI,gBAAA;EnBoxFH;AkBt6EC;EACG,WAAA;ElBw6EJ;AkBt6EC;EACG,QAAA;ElBw6EJ;AkB95ED;EACE,gBAAA;EACA,iBAAA;EACA,qBAAA;EACA,gBAAA;ElBg6ED;AkB70ED;EAAA;IA9DM,uBAAA;IACA,kBAAA;IACA,wBAAA;IlB+4EH;EkBn1EH;IAvDM,uBAAA;IACA,aAAA;IACA,wBAAA;IlB64EH;EkBx1EH;IAhDM,uBAAA;IlB24EH;EkB31EH;IA5CM,uBAAA;IACA,wBAAA;IlB04EH;EkB/1EH;;;IAtCQ,aAAA;IlB04EL;EkBp2EH;IAhCM,aAAA;IlBu4EH;EkBv2EH;IA5BM,kBAAA;IACA,wBAAA;IlBs4EH;EkB32EH;;IApBM,uBAAA;IACA,eAAA;IACA,kBAAA;IACA,wBAAA;IlBm4EH;EkBl3EH;;IAdQ,iBAAA;IlBo4EL;EkBt3EH;;IATM,oBAAA;IACA,gBAAA;IlBm4EH;EkB33EH;IAHM,QAAA;IlBi4EH;EACF;AkBv3ED;;;;EASI,eAAA;EACA,kBAAA;EACA,kBAAA;ElBo3EH;AkB/3ED;;EAiBI,kBAAA;ElBk3EH;AkBn4ED;EJjfE,oBAAA;EACA,qBAAA;Edu3FD;AkBh2EC;EAAA;IAVI,mBAAA;IACA,kBAAA;IACA,kBAAA;IlB82EH;EACF;AkB94ED;EAwCI,aAAA;ElBy2EH;AkB51EC;EAAA;IAHM,0BAAA;IlBm2EL;EACF;AkB11EC;EAAA;IAHM,kBAAA;IlBi2EL;EACF;AoBn5FD;EACE,uBAAA;EACA,kBAAA;EACA,qBAAA;EACA,oBAAA;EACA,wBAAA;EACA,gCAAA;MAAA,4BAAA;EACA,iBAAA;EACA,wBAAA;EACA,+BAAA;EACA,qBAAA;EC6BA,mBAAA;EACA,iBAAA;EACA,yBAAA;EACA,oBAAA;EhB4KA,2BAAA;EACG,wBAAA;EACC,uBAAA;EACI,mBAAA;EL8sFT;AoBt5FG;;;;;;EdrBF,sBAAA;EAEA,4CAAA;EACA,sBAAA;ENk7FD;AoB15FC;;;EAGE,gBAAA;EACA,uBAAA;EpB45FH;AoBz5FC;;EAEE,YAAA;EACA,wBAAA;Ef2BF,0DAAA;EACQ,kDAAA;ELi4FT;AoBz5FC;;;EAGE,qBAAA;EACA,sBAAA;EE9CF,eAAA;EAGA,2BAAA;EjB8DA,0BAAA;EACQ,kBAAA;EL24FT;AoBr5FD;ECrDE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErB68FD;AqB38FC;;;;;;EAME,gBAAA;EACA,2BAAA;EACI,uBAAA;ErB68FP;AqB38FC;;;EAGE,wBAAA;ErB68FH;AqBx8FG;;;;;;;;;;;;;;;;;;EAME,2BAAA;EACI,uBAAA;ErBs9FT;AoB97FD;ECnBI,gBAAA;EACA,2BAAA;ErBo9FH;AoB/7FD;ECxDE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErB0/FD;AqBx/FC;;;;;;EAME,gBAAA;EACA,2BAAA;EACI,uBAAA;ErB0/FP;AqBx/FC;;;EAGE,wBAAA;ErB0/FH;AqBr/FG;;;;;;;;;;;;;;;;;;EAME,2BAAA;EACI,uBAAA;ErBmgGT;AoBx+FD;ECtBI,gBAAA;EACA,2BAAA;ErBigGH;AoBx+FD;EC5DE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErBuiGD;AqBriGC;;;;;;EAME,gBAAA;EACA,2BAAA;EACI,uBAAA;ErBuiGP;AqBriGC;;;EAGE,wBAAA;ErBuiGH;AqBliGG;;;;;;;;;;;;;;;;;;EAME,2BAAA;EACI,uBAAA;ErBgjGT;AoBjhGD;EC1BI,gBAAA;EACA,2BAAA;ErB8iGH;AoBjhGD;EChEE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErBolGD;AqBllGC;;;;;;EAME,gBAAA;EACA,2BAAA;EACI,uBAAA;ErBolGP;AqBllGC;;;EAGE,wBAAA;ErBolGH;AqB/kGG;;;;;;;;;;;;;;;;;;EAME,2BAAA;EACI,uBAAA;ErB6lGT;AoB1jGD;EC9BI,gBAAA;EACA,2BAAA;ErB2lGH;AoB1jGD;ECpEE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErBioGD;AqB/nGC;;;;;;EAME,gBAAA;EACA,2BAAA;EACI,uBAAA;ErBioGP;AqB/nGC;;;EAGE,wBAAA;ErBioGH;AqB5nGG;;;;;;;;;;;;;;;;;;EAME,2BAAA;EACI,uBAAA;ErB0oGT;AoBnmGD;EClCI,gBAAA;EACA,2BAAA;ErBwoGH;AoBnmGD;ECxEE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErB8qGD;AqB5qGC;;;;;;EAME,gBAAA;EACA,2BAAA;EACI,uBAAA;ErB8qGP;AqB5qGC;;;EAGE,wBAAA;ErB8qGH;AqBzqGG;;;;;;;;;;;;;;;;;;EAME,2BAAA;EACI,uBAAA;ErBurGT;AoB5oGD;ECtCI,gBAAA;EACA,2BAAA;ErBqrGH;AoBvoGD;EACE,gBAAA;EACA,qBAAA;EACA,kBAAA;EpByoGD;AoBvoGC;;;;;EAKE,+BAAA;Ef7BF,0BAAA;EACQ,kBAAA;ELuqGT;AoBxoGC;;;;EAIE,2BAAA;EpB0oGH;AoBxoGC;;EAEE,gBAAA;EACA,4BAAA;EACA,+BAAA;EpB0oGH;AoBtoGG;;;;EAEE,gBAAA;EACA,uBAAA;EpB0oGL;AoBjoGD;;EC/EE,oBAAA;EACA,iBAAA;EACA,wBAAA;EACA,oBAAA;ErBotGD;AoBpoGD;;ECnFE,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,oBAAA;ErB2tGD;AoBvoGD;;ECvFE,kBAAA;EACA,iBAAA;EACA,kBAAA;EACA,oBAAA;ErBkuGD;AoBtoGD;EACE,gBAAA;EACA,aAAA;EpBwoGD;AoBpoGD;EACE,iBAAA;EpBsoGD;AoB/nGC;;;EACE,aAAA;EpBmoGH;AuBvxGD;EACE,YAAA;ElBoLA,0CAAA;EACK,qCAAA;EACG,kCAAA;ELsmGT;AuB1xGC;EACE,YAAA;EvB4xGH;AuBxxGD;EACE,eAAA;EvB0xGD;AuBxxGC;EAAY,gBAAA;EvB2xGb;AuB1xGC;EAAY,oBAAA;EvB6xGb;AuB5xGC;EAAY,0BAAA;EvB+xGb;AuB5xGD;EACE,oBAAA;EACA,WAAA;EACA,kBAAA;ElBuKA,iDAAA;EACQ,4CAAA;KAAA,yCAAA;EAOR,oCAAA;EACQ,+BAAA;KAAA,4BAAA;EAGR,0CAAA;EACQ,qCAAA;KAAA,kCAAA;ELgnGT;AwB1zGD;EACE,uBAAA;EACA,UAAA;EACA,WAAA;EACA,kBAAA;EACA,wBAAA;EACA,wBAAA;EACA,qCAAA;EACA,oCAAA;ExB4zGD;AwBxzGD;;EAEE,oBAAA;ExB0zGD;AwBtzGD;EACE,YAAA;ExBwzGD;AwBpzGD;EACE,oBAAA;EACA,WAAA;EACA,SAAA;EACA,eAAA;EACA,eAAA;EACA,aAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,kBAAA;EACA,iBAAA;EACA,kBAAA;EACA,2BAAA;EACA,2BAAA;EACA,uCAAA;EACA,oBAAA;EnBuBA,qDAAA;EACQ,6CAAA;EmBtBR,sCAAA;UAAA,8BAAA;ExBuzGD;AwBlzGC;EACE,UAAA;EACA,YAAA;ExBozGH;AwB70GD;ECxBE,aAAA;EACA,eAAA;EACA,kBAAA;EACA,2BAAA;EzBw2GD;AwBn1GD;EAmCI,gBAAA;EACA,mBAAA;EACA,aAAA;EACA,qBAAA;EACA,yBAAA;EACA,gBAAA;EACA,qBAAA;ExBmzGH;AwB7yGC;;EAEE,uBAAA;EACA,gBAAA;EACA,2BAAA;ExB+yGH;AwBzyGC;;;EAGE,gBAAA;EACA,uBAAA;EACA,YAAA;EACA,2BAAA;ExB2yGH;AwBlyGC;;;EAGE,gBAAA;ExBoyGH;AwBhyGC;;EAEE,uBAAA;EACA,+BAAA;EACA,wBAAA;EE1GF,qEAAA;EF4GE,qBAAA;ExBkyGH;AwB7xGD;EAGI,gBAAA;ExB6xGH;AwBhyGD;EAQI,YAAA;ExB2xGH;AwBnxGD;EACE,YAAA;EACA,UAAA;ExBqxGD;AwB7wGD;EACE,SAAA;EACA,aAAA;ExB+wGD;AwB3wGD;EACE,gBAAA;EACA,mBAAA;EACA,iBAAA;EACA,yBAAA;EACA,gBAAA;EACA,qBAAA;ExB6wGD;AwBzwGD;EACE,iBAAA;EACA,SAAA;EACA,UAAA;EACA,WAAA;EACA,QAAA;EACA,cAAA;ExB2wGD;AwBvwGD;EACE,UAAA;EACA,YAAA;ExBywGD;AwBjwGD;;EAII,eAAA;EACA,0BAAA;EACA,aAAA;ExBiwGH;AwBvwGD;;EAUI,WAAA;EACA,cAAA;EACA,oBAAA;ExBiwGH;AwB5uGD;EAXE;IAnEA,YAAA;IACA,UAAA;IxB8zGC;EwB5vGD;IAzDA,SAAA;IACA,aAAA;IxBwzGC;EACF;A2Bv8GD;;EAEE,oBAAA;EACA,uBAAA;EACA,wBAAA;E3By8GD;A2B78GD;;EAMI,oBAAA;EACA,aAAA;E3B28GH;A2Bz8GG;;;;;;;;EAIE,YAAA;E3B+8GL;A2Bz8GD;;;;EAKI,mBAAA;E3B08GH;A2Br8GD;EACE,mBAAA;E3Bu8GD;A2Bx8GD;;EAMI,aAAA;E3Bs8GH;A2B58GD;;;EAWI,kBAAA;E3Bs8GH;A2Bl8GD;EACE,kBAAA;E3Bo8GD;A2Bh8GD;EACE,gBAAA;E3Bk8GD;A2Bj8GC;ECjDA,+BAAA;EACG,4BAAA;E5Bq/GJ;A2Bh8GD;;EC9CE,8BAAA;EACG,2BAAA;E5Bk/GJ;A2B/7GD;EACE,aAAA;E3Bi8GD;A2B/7GD;EACE,kBAAA;E3Bi8GD;A2B/7GD;;EClEE,+BAAA;EACG,4BAAA;E5BqgHJ;A2B97GD;EChEE,8BAAA;EACG,2BAAA;E5BigHJ;A2B77GD;;EAEE,YAAA;E3B+7GD;A2B96GD;EACE,mBAAA;EACA,oBAAA;E3Bg7GD;A2B96GD;EACE,oBAAA;EACA,qBAAA;E3Bg7GD;A2B36GD;EtB9CE,0DAAA;EACQ,kDAAA;EL49GT;A2B36GC;EtBlDA,0BAAA;EACQ,kBAAA;ELg+GT;A2Bx6GD;EACE,gBAAA;E3B06GD;A2Bv6GD;EACE,yBAAA;EACA,wBAAA;E3By6GD;A2Bt6GD;EACE,yBAAA;E3Bw6GD;A2Bj6GD;;;EAII,gBAAA;EACA,aAAA;EACA,aAAA;EACA,iBAAA;E3Bk6GH;A2Bz6GD;EAcM,aAAA;E3B85GL;A2B56GD;;;;EAsBI,kBAAA;EACA,gBAAA;E3B45GH;A2Bv5GC;EACE,kBAAA;E3By5GH;A2Bv5GC;EACE,8BAAA;ECnKF,+BAAA;EACC,8BAAA;E5B6jHF;A2Bx5GC;EACE,gCAAA;EC/KF,4BAAA;EACC,2BAAA;E5B0kHF;A2Bx5GD;EACE,kBAAA;E3B05GD;A2Bx5GD;;EC9KE,+BAAA;EACC,8BAAA;E5B0kHF;A2Bv5GD;EC5LE,4BAAA;EACC,2BAAA;E5BslHF;A2Bn5GD;EACE,gBAAA;EACA,aAAA;EACA,qBAAA;EACA,2BAAA;E3Bq5GD;A2Bz5GD;;EAOI,aAAA;EACA,qBAAA;EACA,WAAA;E3Bs5GH;A2B/5GD;EAYI,aAAA;E3Bs5GH;A2Bl6GD;EAgBI,YAAA;E3Bq5GH;A2Bp4GD;;;;EAKM,oBAAA;EACA,wBAAA;EACA,sBAAA;E3Bq4GL;A6B9mHD;EACE,oBAAA;EACA,gBAAA;EACA,2BAAA;E7BgnHD;A6B7mHC;EACE,aAAA;EACA,iBAAA;EACA,kBAAA;E7B+mHH;A6BxnHD;EAeI,oBAAA;EACA,YAAA;EAKA,aAAA;EAEA,aAAA;EACA,kBAAA;E7BumHH;A6B9lHD;;;EV8BE,cAAA;EACA,oBAAA;EACA,iBAAA;EACA,wBAAA;EACA,oBAAA;EnBqkHD;AmBnkHC;;;EACE,cAAA;EACA,mBAAA;EnBukHH;AmBpkHC;;;;;;EAEE,cAAA;EnB0kHH;A6BhnHD;;;EVyBE,cAAA;EACA,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,oBAAA;EnB4lHD;AmB1lHC;;;EACE,cAAA;EACA,mBAAA;EnB8lHH;AmB3lHC;;;;;;EAEE,cAAA;EnBimHH;A6B9nHD;;;EAGE,qBAAA;E7BgoHD;A6B9nHC;;;EACE,kBAAA;E7BkoHH;A6B9nHD;;EAEE,WAAA;EACA,qBAAA;EACA,wBAAA;E7BgoHD;A6B3nHD;EACE,mBAAA;EACA,iBAAA;EACA,qBAAA;EACA,gBAAA;EACA,gBAAA;EACA,oBAAA;EACA,2BAAA;EACA,2BAAA;EACA,oBAAA;E7B6nHD;A6B1nHC;EACE,mBAAA;EACA,iBAAA;EACA,oBAAA;E7B4nHH;A6B1nHC;EACE,oBAAA;EACA,iBAAA;EACA,oBAAA;E7B4nHH;A6BhpHD;;EA0BI,eAAA;E7B0nHH;A6BrnHD;;;;;;;EDhGE,+BAAA;EACG,4BAAA;E5B8tHJ;A6BtnHD;EACE,iBAAA;E7BwnHD;A6BtnHD;;;;;;;EDpGE,8BAAA;EACG,2BAAA;E5BmuHJ;A6BvnHD;EACE,gBAAA;E7BynHD;A6BpnHD;EACE,oBAAA;EAGA,cAAA;EACA,qBAAA;E7BonHD;A6BznHD;EAUI,oBAAA;E7BknHH;A6B5nHD;EAYM,mBAAA;E7BmnHL;A6BhnHG;;;EAGE,YAAA;E7BknHL;A6B7mHC;;EAGI,oBAAA;E7B8mHL;A6B3mHC;;EAGI,mBAAA;E7B4mHL;A8BtwHD;EACE,kBAAA;EACA,iBAAA;EACA,kBAAA;E9BwwHD;A8B3wHD;EAOI,oBAAA;EACA,gBAAA;E9BuwHH;A8B/wHD;EAWM,oBAAA;EACA,gBAAA;EACA,oBAAA;E9BuwHL;A8BtwHK;;EAEE,uBAAA;EACA,2BAAA;E9BwwHP;A8BnwHG;EACE,gBAAA;E9BqwHL;A8BnwHK;;EAEE,gBAAA;EACA,uBAAA;EACA,+BAAA;EACA,qBAAA;E9BqwHP;A8B9vHG;;;EAGE,2BAAA;EACA,uBAAA;E9BgwHL;A8BzyHD;ELHE,aAAA;EACA,eAAA;EACA,kBAAA;EACA,2BAAA;EzB+yHD;A8B/yHD;EA0DI,iBAAA;E9BwvHH;A8B/uHD;EACE,kCAAA;E9BivHD;A8BlvHD;EAGI,aAAA;EAEA,qBAAA;E9BivHH;A8BtvHD;EASM,mBAAA;EACA,yBAAA;EACA,+BAAA;EACA,4BAAA;E9BgvHL;A8B/uHK;EACE,uCAAA;E9BivHP;A8B3uHK;;;EAGE,gBAAA;EACA,2BAAA;EACA,2BAAA;EACA,kCAAA;EACA,iBAAA;E9B6uHP;A8BxuHC;EAqDA,aAAA;EA8BA,kBAAA;E9BypHD;A8B5uHC;EAwDE,aAAA;E9BurHH;A8B/uHC;EA0DI,oBAAA;EACA,oBAAA;E9BwrHL;A8BnvHC;EAgEE,WAAA;EACA,YAAA;E9BsrHH;A8B1qHD;EAAA;IAPM,qBAAA;IACA,WAAA;I9BqrHH;E8B/qHH;IAJQ,kBAAA;I9BsrHL;EACF;A8BhwHC;EAuFE,iBAAA;EACA,oBAAA;E9B4qHH;A8BpwHC;;;EA8FE,2BAAA;E9B2qHH;A8B7pHD;EAAA;IATM,kCAAA;IACA,4BAAA;I9B0qHH;E8BlqHH;;;IAHM,8BAAA;I9B0qHH;EACF;A8B3wHD;EAEI,aAAA;E9B4wHH;A8B9wHD;EAMM,oBAAA;E9B2wHL;A8BjxHD;EASM,kBAAA;E9B2wHL;A8BtwHK;;;EAGE,gBAAA;EACA,2BAAA;E9BwwHP;A8BhwHD;EAEI,aAAA;E9BiwHH;A8BnwHD;EAIM,iBAAA;EACA,gBAAA;E9BkwHL;A8BtvHD;EACE,aAAA;E9BwvHD;A8BzvHD;EAII,aAAA;E9BwvHH;A8B5vHD;EAMM,oBAAA;EACA,oBAAA;E9ByvHL;A8BhwHD;EAYI,WAAA;EACA,YAAA;E9BuvHH;A8B3uHD;EAAA;IAPM,qBAAA;IACA,WAAA;I9BsvHH;E8BhvHH;IAJQ,kBAAA;I9BuvHL;EACF;A8B/uHD;EACE,kBAAA;E9BivHD;A8BlvHD;EAKI,iBAAA;EACA,oBAAA;E9BgvHH;A8BtvHD;;;EAYI,2BAAA;E9B+uHH;A8BjuHD;EAAA;IATM,kCAAA;IACA,4BAAA;I9B8uHH;E8BtuHH;;;IAHM,8BAAA;I9B8uHH;EACF;A8BruHD;EAEI,eAAA;E9BsuHH;A8BxuHD;EAKI,gBAAA;E9BsuHH;A8B7tHD;EAEE,kBAAA;EF3OA,4BAAA;EACC,2BAAA;E5B08HF;A+Bp8HD;EACE,oBAAA;EACA,kBAAA;EACA,qBAAA;EACA,+BAAA;E/Bs8HD;A+B97HD;EAAA;IAFI,oBAAA;I/Bo8HD;EACF;A+Br7HD;EAAA;IAFI,aAAA;I/B27HD;EACF;A+B76HD;EACE,qBAAA;EACA,qBAAA;EACA,oBAAA;EACA,mCAAA;EACA,4DAAA;UAAA,oDAAA;EAEA,mCAAA;E/B86HD;A+B56HC;EACE,kBAAA;E/B86HH;A+Bl5HD;EAAA;IAxBI,aAAA;IACA,eAAA;IACA,0BAAA;YAAA,kBAAA;I/B86HD;E+B56HC;IACE,2BAAA;IACA,yBAAA;IACA,mBAAA;IACA,8BAAA;I/B86HH;E+B36HC;IACE,qBAAA;I/B66HH;E+Bx6HC;;;IAGE,iBAAA;IACA,kBAAA;I/B06HH;EACF;A+Bt6HD;;EAGI,mBAAA;E/Bu6HH;A+Bl6HC;EAAA;;IAFI,mBAAA;I/By6HH;EACF;A+Bh6HD;;;;EAII,qBAAA;EACA,oBAAA;E/Bk6HH;A+B55HC;EAAA;;;;IAHI,iBAAA;IACA,gBAAA;I/Bs6HH;EACF;A+B15HD;EACE,eAAA;EACA,uBAAA;E/B45HD;A+Bv5HD;EAAA;IAFI,kBAAA;I/B65HD;EACF;A+Bz5HD;;EAEE,iBAAA;EACA,UAAA;EACA,SAAA;EACA,eAAA;E/B25HD;A+Br5HD;EAAA;;IAFI,kBAAA;I/B45HD;EACF;A+B15HD;EACE,QAAA;EACA,uBAAA;E/B45HD;A+B15HD;EACE,WAAA;EACA,kBAAA;EACA,uBAAA;E/B45HD;A+Bt5HD;EACE,aAAA;EACA,oBAAA;EACA,iBAAA;EACA,mBAAA;EACA,cAAA;E/Bw5HD;A+Bt5HC;;EAEE,uBAAA;E/Bw5HH;A+Bj6HD;EAaI,gBAAA;E/Bu5HH;A+B94HD;EALI;;IAEE,oBAAA;I/Bs5HH;EACF;A+B54HD;EACE,oBAAA;EACA,cAAA;EACA,oBAAA;EACA,mBAAA;EC9LA,iBAAA;EACA,oBAAA;ED+LA,+BAAA;EACA,wBAAA;EACA,+BAAA;EACA,oBAAA;E/B+4HD;A+B34HC;EACE,YAAA;E/B64HH;A+B35HD;EAmBI,gBAAA;EACA,aAAA;EACA,aAAA;EACA,oBAAA;E/B24HH;A+Bj6HD;EAyBI,iBAAA;E/B24HH;A+Br4HD;EAAA;IAFI,eAAA;I/B24HD;EACF;A+Bl4HD;EACE,qBAAA;E/Bo4HD;A+Br4HD;EAII,mBAAA;EACA,sBAAA;EACA,mBAAA;E/Bo4HH;A+Bx2HC;EAAA;IAtBI,kBAAA;IACA,aAAA;IACA,aAAA;IACA,eAAA;IACA,+BAAA;IACA,WAAA;IACA,0BAAA;YAAA,kBAAA;I/Bk4HH;E+Bl3HD;;IAbM,4BAAA;I/Bm4HL;E+Bt3HD;IAVM,mBAAA;I/Bm4HL;E+Bl4HK;;IAEE,wBAAA;I/Bo4HP;EACF;A+Bl3HD;EAAA;IAXI,aAAA;IACA,WAAA;I/Bi4HD;E+Bv3HH;IAPM,aAAA;I/Bi4HH;E+B13HH;IALQ,mBAAA;IACA,sBAAA;I/Bk4HL;EACF;A+Bv3HD;EACE,oBAAA;EACA,qBAAA;EACA,oBAAA;EACA,mCAAA;EACA,sCAAA;E1B9NA,8FAAA;EACQ,sFAAA;E2B/DR,iBAAA;EACA,oBAAA;EhCwpID;AkBvqHD;EAAA;IA9DM,uBAAA;IACA,kBAAA;IACA,wBAAA;IlByuHH;EkB7qHH;IAvDM,uBAAA;IACA,aAAA;IACA,wBAAA;IlBuuHH;EkBlrHH;IAhDM,uBAAA;IlBquHH;EkBrrHH;IA5CM,uBAAA;IACA,wBAAA;IlBouHH;EkBzrHH;;;IAtCQ,aAAA;IlBouHL;EkB9rHH;IAhCM,aAAA;IlBiuHH;EkBjsHH;IA5BM,kBAAA;IACA,wBAAA;IlBguHH;EkBrsHH;;IApBM,uBAAA;IACA,eAAA;IACA,kBAAA;IACA,wBAAA;IlB6tHH;EkB5sHH;;IAdQ,iBAAA;IlB8tHL;EkBhtHH;;IATM,oBAAA;IACA,gBAAA;IlB6tHH;EkBrtHH;IAHM,QAAA;IlB2tHH;EACF;A+Bh6HC;EAAA;IANI,oBAAA;I/B06HH;E+Bx6HG;IACE,kBAAA;I/B06HL;EACF;A+Bz5HD;EAAA;IARI,aAAA;IACA,WAAA;IACA,gBAAA;IACA,iBAAA;IACA,gBAAA;IACA,mBAAA;I1BzPF,0BAAA;IACQ,kBAAA;IL+pIP;EACF;A+B/5HD;EACE,eAAA;EHpUA,4BAAA;EACC,2BAAA;E5BsuIF;A+B/5HD;EACE,kBAAA;EHzUA,8BAAA;EACC,6BAAA;EAOD,+BAAA;EACC,8BAAA;E5BquIF;A+B35HD;EChVE,iBAAA;EACA,oBAAA;EhC8uID;A+B55HC;ECnVA,kBAAA;EACA,qBAAA;EhCkvID;A+B75HC;ECtVA,kBAAA;EACA,qBAAA;EhCsvID;A+Bv5HD;EChWE,kBAAA;EACA,qBAAA;EhC0vID;A+Bn5HD;EAAA;IAJI,aAAA;IACA,mBAAA;IACA,oBAAA;I/B25HD;EACF;A+B93HD;EAhBE;IExWA,wBAAA;IjC0vIC;E+Bj5HD;IE5WA,yBAAA;IF8WE,qBAAA;I/Bm5HD;E+Br5HD;IAKI,iBAAA;I/Bm5HH;EACF;A+B14HD;EACE,2BAAA;EACA,uBAAA;E/B44HD;A+B94HD;EAKI,gBAAA;E/B44HH;A+B34HG;;EAEE,gBAAA;EACA,+BAAA;E/B64HL;A+Bt5HD;EAcI,gBAAA;E/B24HH;A+Bz5HD;EAmBM,gBAAA;E/By4HL;A+Bv4HK;;EAEE,gBAAA;EACA,+BAAA;E/By4HP;A+Br4HK;;;EAGE,gBAAA;EACA,2BAAA;E/Bu4HP;A+Bn4HK;;;EAGE,gBAAA;EACA,+BAAA;E/Bq4HP;A+B76HD;EA8CI,uBAAA;E/Bk4HH;A+Bj4HG;;EAEE,2BAAA;E/Bm4HL;A+Bp7HD;EAoDM,2BAAA;E/Bm4HL;A+Bv7HD;;EA0DI,uBAAA;E/Bi4HH;A+B13HK;;;EAGE,2BAAA;EACA,gBAAA;E/B43HP;A+B31HC;EAAA;IAzBQ,gBAAA;I/Bw3HP;E+Bv3HO;;IAEE,gBAAA;IACA,+BAAA;I/By3HT;E+Br3HO;;;IAGE,gBAAA;IACA,2BAAA;I/Bu3HT;E+Bn3HO;;;IAGE,gBAAA;IACA,+BAAA;I/Bq3HT;EACF;A+Bv9HD;EA8GI,gBAAA;E/B42HH;A+B32HG;EACE,gBAAA;E/B62HL;A+B79HD;EAqHI,gBAAA;E/B22HH;A+B12HG;;EAEE,gBAAA;E/B42HL;A+Bx2HK;;;;EAEE,gBAAA;E/B42HP;A+Bp2HD;EACE,2BAAA;EACA,uBAAA;E/Bs2HD;A+Bx2HD;EAKI,gBAAA;E/Bs2HH;A+Br2HG;;EAEE,gBAAA;EACA,+BAAA;E/Bu2HL;A+Bh3HD;EAcI,gBAAA;E/Bq2HH;A+Bn3HD;EAmBM,gBAAA;E/Bm2HL;A+Bj2HK;;EAEE,gBAAA;EACA,+BAAA;E/Bm2HP;A+B/1HK;;;EAGE,gBAAA;EACA,2BAAA;E/Bi2HP;A+B71HK;;;EAGE,gBAAA;EACA,+BAAA;E/B+1HP;A+Bv4HD;EA+CI,uBAAA;E/B21HH;A+B11HG;;EAEE,2BAAA;E/B41HL;A+B94HD;EAqDM,2BAAA;E/B41HL;A+Bj5HD;;EA2DI,uBAAA;E/B01HH;A+Bp1HK;;;EAGE,2BAAA;EACA,gBAAA;E/Bs1HP;A+B/yHC;EAAA;IA/BQ,uBAAA;I/Bk1HP;E+BnzHD;IA5BQ,2BAAA;I/Bk1HP;E+BtzHD;IAzBQ,gBAAA;I/Bk1HP;E+Bj1HO;;IAEE,gBAAA;IACA,+BAAA;I/Bm1HT;E+B/0HO;;;IAGE,gBAAA;IACA,2BAAA;I/Bi1HT;E+B70HO;;;IAGE,gBAAA;IACA,+BAAA;I/B+0HT;EACF;A+Bv7HD;EA+GI,gBAAA;E/B20HH;A+B10HG;EACE,gBAAA;E/B40HL;A+B77HD;EAsHI,gBAAA;E/B00HH;A+Bz0HG;;EAEE,gBAAA;E/B20HL;A+Bv0HK;;;;EAEE,gBAAA;E/B20HP;AkCr9ID;EACE,mBAAA;EACA,qBAAA;EACA,kBAAA;EACA,2BAAA;EACA,oBAAA;ElCu9ID;AkC59ID;EAQI,uBAAA;ElCu9IH;AkC/9ID;EAWM,mBAAA;EACA,gBAAA;EACA,gBAAA;ElCu9IL;AkCp+ID;EAkBI,gBAAA;ElCq9IH;AmCz+ID;EACE,uBAAA;EACA,iBAAA;EACA,gBAAA;EACA,oBAAA;EnC2+ID;AmC/+ID;EAOI,iBAAA;EnC2+IH;AmCl/ID;;EAUM,oBAAA;EACA,aAAA;EACA,mBAAA;EACA,yBAAA;EACA,uBAAA;EACA,gBAAA;EACA,2BAAA;EACA,2BAAA;EACA,mBAAA;EnC4+IL;AmC1+IG;;EAGI,gBAAA;EPXN,gCAAA;EACG,6BAAA;E5Bu/IJ;AmCz+IG;;EPvBF,iCAAA;EACG,8BAAA;E5BogJJ;AmCp+IG;;;;EAEE,gBAAA;EACA,2BAAA;EACA,uBAAA;EnCw+IL;AmCl+IG;;;;;;EAGE,YAAA;EACA,gBAAA;EACA,2BAAA;EACA,uBAAA;EACA,iBAAA;EnCu+IL;AmC7hJD;;;;;;EAiEM,gBAAA;EACA,2BAAA;EACA,uBAAA;EACA,qBAAA;EnCo+IL;AmC39ID;;EC1EM,oBAAA;EACA,iBAAA;EpCyiJL;AoCviJG;;ERMF,gCAAA;EACG,6BAAA;E5BqiJJ;AoCtiJG;;ERRF,iCAAA;EACG,8BAAA;E5BkjJJ;AmCr+ID;;EC/EM,mBAAA;EACA,iBAAA;EpCwjJL;AoCtjJG;;ERMF,gCAAA;EACG,6BAAA;E5BojJJ;AoCrjJG;;ERRF,iCAAA;EACG,8BAAA;E5BikJJ;AqCpkJD;EACE,iBAAA;EACA,gBAAA;EACA,kBAAA;EACA,oBAAA;ErCskJD;AqC1kJD;EAOI,iBAAA;ErCskJH;AqC7kJD;;EAUM,uBAAA;EACA,mBAAA;EACA,2BAAA;EACA,2BAAA;EACA,qBAAA;ErCukJL;AqCrlJD;;EAmBM,uBAAA;EACA,2BAAA;ErCskJL;AqC1lJD;;EA2BM,cAAA;ErCmkJL;AqC9lJD;;EAkCM,aAAA;ErCgkJL;AqClmJD;;;;EA2CM,gBAAA;EACA,2BAAA;EACA,qBAAA;ErC6jJL;AsC3mJD;EACE,iBAAA;EACA,yBAAA;EACA,gBAAA;EACA,mBAAA;EACA,gBAAA;EACA,gBAAA;EACA,oBAAA;EACA,qBAAA;EACA,0BAAA;EACA,sBAAA;EtC6mJD;AsCzmJG;;EAEE,gBAAA;EACA,uBAAA;EACA,iBAAA;EtC2mJL;AsCtmJC;EACE,eAAA;EtCwmJH;AsCpmJC;EACE,oBAAA;EACA,WAAA;EtCsmJH;AsC/lJD;ECtCE,2BAAA;EvCwoJD;AuCroJG;;EAEE,2BAAA;EvCuoJL;AsClmJD;EC1CE,2BAAA;EvC+oJD;AuC5oJG;;EAEE,2BAAA;EvC8oJL;AsCrmJD;EC9CE,2BAAA;EvCspJD;AuCnpJG;;EAEE,2BAAA;EvCqpJL;AsCxmJD;EClDE,2BAAA;EvC6pJD;AuC1pJG;;EAEE,2BAAA;EvC4pJL;AsC3mJD;ECtDE,2BAAA;EvCoqJD;AuCjqJG;;EAEE,2BAAA;EvCmqJL;AsC9mJD;EC1DE,2BAAA;EvC2qJD;AuCxqJG;;EAEE,2BAAA;EvC0qJL;AwC5qJD;EACE,uBAAA;EACA,iBAAA;EACA,kBAAA;EACA,iBAAA;EACA,mBAAA;EACA,gBAAA;EACA,gBAAA;EACA,0BAAA;EACA,qBAAA;EACA,oBAAA;EACA,2BAAA;EACA,qBAAA;ExC8qJD;AwC3qJC;EACE,eAAA;ExC6qJH;AwCzqJC;EACE,oBAAA;EACA,WAAA;ExC2qJH;AwCxqJC;;EAEE,QAAA;EACA,kBAAA;ExC0qJH;AwCrqJG;;EAEE,gBAAA;EACA,uBAAA;EACA,iBAAA;ExCuqJL;AwClqJC;;EAEE,gBAAA;EACA,2BAAA;ExCoqJH;AwCjqJC;EACE,cAAA;ExCmqJH;AwChqJC;EACE,mBAAA;ExCkqJH;AwC/pJC;EACE,kBAAA;ExCiqJH;AyC3tJD;EACE,oBAAA;EACA,qBAAA;EACA,gBAAA;EACA,2BAAA;EzC6tJD;AyCjuJD;;EAQI,gBAAA;EzC6tJH;AyCruJD;EAYI,qBAAA;EACA,iBAAA;EACA,kBAAA;EzC4tJH;AyC1uJD;EAkBI,2BAAA;EzC2tJH;AyCxtJC;;EAEE,oBAAA;EzC0tJH;AyCjvJD;EA2BI,iBAAA;EzCytJH;AyCxsJD;EAAA;IAbI,iBAAA;IzCytJD;EyCvtJC;;IAEE,oBAAA;IACA,qBAAA;IzCytJH;EyCjtJH;;IAHM,iBAAA;IzCwtJH;EACF;A0CjwJD;EACE,gBAAA;EACA,cAAA;EACA,qBAAA;EACA,yBAAA;EACA,2BAAA;EACA,2BAAA;EACA,oBAAA;ErCiLA,6CAAA;EACK,wCAAA;EACG,qCAAA;ELmlJT;A0C7wJD;;EAaI,mBAAA;EACA,oBAAA;E1CowJH;A0ChwJC;;;EAGE,uBAAA;E1CkwJH;A0CvxJD;EA0BI,cAAA;EACA,gBAAA;E1CgwJH;A2CzxJD;EACE,eAAA;EACA,qBAAA;EACA,+BAAA;EACA,oBAAA;E3C2xJD;A2C/xJD;EAQI,eAAA;EAEA,gBAAA;E3CyxJH;A2CnyJD;EAeI,mBAAA;E3CuxJH;A2CtyJD;;EAqBI,kBAAA;E3CqxJH;A2C1yJD;EAyBI,iBAAA;E3CoxJH;A2C5wJD;;EAEE,qBAAA;E3C8wJD;A2ChxJD;;EAMI,oBAAA;EACA,WAAA;EACA,cAAA;EACA,gBAAA;E3C8wJH;A2CtwJD;ECvDE,2BAAA;EACA,uBAAA;EACA,gBAAA;E5Cg0JD;A2C3wJD;EClDI,2BAAA;E5Cg0JH;A2C9wJD;EC/CI,gBAAA;E5Cg0JH;A2C7wJD;EC3DE,2BAAA;EACA,uBAAA;EACA,gBAAA;E5C20JD;A2ClxJD;ECtDI,2BAAA;E5C20JH;A2CrxJD;ECnDI,gBAAA;E5C20JH;A2CpxJD;EC/DE,2BAAA;EACA,uBAAA;EACA,gBAAA;E5Cs1JD;A2CzxJD;EC1DI,2BAAA;E5Cs1JH;A2C5xJD;ECvDI,gBAAA;E5Cs1JH;A2C3xJD;ECnEE,2BAAA;EACA,uBAAA;EACA,gBAAA;E5Ci2JD;A2ChyJD;EC9DI,2BAAA;E5Ci2JH;A2CnyJD;EC3DI,gBAAA;E5Ci2JH;A6Cn2JD;EACE;IAAQ,6BAAA;I7Cs2JP;E6Cr2JD;IAAQ,0BAAA;I7Cw2JP;EACF;A6Cr2JD;EACE;IAAQ,6BAAA;I7Cw2JP;E6Cv2JD;IAAQ,0BAAA;I7C02JP;EACF;A6C72JD;EACE;IAAQ,6BAAA;I7Cw2JP;E6Cv2JD;IAAQ,0BAAA;I7C02JP;EACF;A6Cn2JD;EACE,kBAAA;EACA,cAAA;EACA,qBAAA;EACA,2BAAA;EACA,oBAAA;ExCsCA,wDAAA;EACQ,gDAAA;ELg0JT;A6Cl2JD;EACE,aAAA;EACA,WAAA;EACA,cAAA;EACA,iBAAA;EACA,mBAAA;EACA,gBAAA;EACA,oBAAA;EACA,2BAAA;ExCyBA,wDAAA;EACQ,gDAAA;EAyHR,qCAAA;EACK,gCAAA;EACG,6BAAA;ELotJT;A6C/1JD;;ECCI,+MAAA;EACA,0MAAA;EACA,uMAAA;EDAF,oCAAA;UAAA,4BAAA;E7Cm2JD;A6C51JD;;ExC5CE,4DAAA;EACK,uDAAA;EACG,oDAAA;EL44JT;A6Cz1JD;EErEE,2BAAA;E/Ci6JD;A+C95JC;EDgDE,+MAAA;EACA,0MAAA;EACA,uMAAA;E9Ci3JH;A6C71JD;EEzEE,2BAAA;E/Cy6JD;A+Ct6JC;EDgDE,+MAAA;EACA,0MAAA;EACA,uMAAA;E9Cy3JH;A6Cj2JD;EE7EE,2BAAA;E/Ci7JD;A+C96JC;EDgDE,+MAAA;EACA,0MAAA;EACA,uMAAA;E9Ci4JH;A6Cr2JD;EEjFE,2BAAA;E/Cy7JD;A+Ct7JC;EDgDE,+MAAA;EACA,0MAAA;EACA,uMAAA;E9Cy4JH;AgDj8JD;EAEE,kBAAA;EhDk8JD;AgDh8JC;EACE,eAAA;EhDk8JH;AgD97JD;;EAEE,SAAA;EACA,kBAAA;EhDg8JD;AgD77JD;EACE,gBAAA;EhD+7JD;AgD57JD;EACE,gBAAA;EhD87JD;AgD37JD;;EAEE,oBAAA;EhD67JD;AgD17JD;;EAEE,qBAAA;EhD47JD;AgDz7JD;;;EAGE,qBAAA;EACA,qBAAA;EhD27JD;AgDx7JD;EACE,wBAAA;EhD07JD;AgDv7JD;EACE,wBAAA;EhDy7JD;AgDr7JD;EACE,eAAA;EACA,oBAAA;EhDu7JD;AgDj7JD;EACE,iBAAA;EACA,kBAAA;EhDm7JD;AiDr+JD;EAEE,qBAAA;EACA,iBAAA;EjDs+JD;AiD99JD;EACE,oBAAA;EACA,gBAAA;EACA,oBAAA;EAEA,qBAAA;EACA,2BAAA;EACA,2BAAA;EjD+9JD;AiD59JC;ErB3BA,8BAAA;EACC,6BAAA;E5B0/JF;AiD79JC;EACE,kBAAA;ErBvBF,iCAAA;EACC,gCAAA;E5Bu/JF;AiDt9JD;EACE,gBAAA;EjDw9JD;AiDz9JD;EAII,gBAAA;EjDw9JH;AiDp9JC;;EAEE,uBAAA;EACA,gBAAA;EACA,2BAAA;EjDs9JH;AiDh9JC;;;EAGE,2BAAA;EACA,gBAAA;EACA,qBAAA;EjDk9JH;AiDv9JC;;;EASI,gBAAA;EjDm9JL;AiD59JC;;;EAYI,gBAAA;EjDq9JL;AiDh9JC;;;EAGE,YAAA;EACA,gBAAA;EACA,2BAAA;EACA,uBAAA;EjDk9JH;AiDx9JC;;;;;;;;;EAYI,gBAAA;EjDu9JL;AiDn+JC;;;EAeI,gBAAA;EjDy9JL;AkDrjKC;EACE,gBAAA;EACA,2BAAA;ElDujKH;AkDrjKG;EACE,gBAAA;ElDujKL;AkDxjKG;EAII,gBAAA;ElDujKP;AkDpjKK;;EAEE,gBAAA;EACA,2BAAA;ElDsjKP;AkDpjKK;;;EAGE,aAAA;EACA,2BAAA;EACA,uBAAA;ElDsjKP;AkD3kKC;EACE,gBAAA;EACA,2BAAA;ElD6kKH;AkD3kKG;EACE,gBAAA;ElD6kKL;AkD9kKG;EAII,gBAAA;ElD6kKP;AkD1kKK;;EAEE,gBAAA;EACA,2BAAA;ElD4kKP;AkD1kKK;;;EAGE,aAAA;EACA,2BAAA;EACA,uBAAA;ElD4kKP;AkDjmKC;EACE,gBAAA;EACA,2BAAA;ElDmmKH;AkDjmKG;EACE,gBAAA;ElDmmKL;AkDpmKG;EAII,gBAAA;ElDmmKP;AkDhmKK;;EAEE,gBAAA;EACA,2BAAA;ElDkmKP;AkDhmKK;;;EAGE,aAAA;EACA,2BAAA;EACA,uBAAA;ElDkmKP;AkDvnKC;EACE,gBAAA;EACA,2BAAA;ElDynKH;AkDvnKG;EACE,gBAAA;ElDynKL;AkD1nKG;EAII,gBAAA;ElDynKP;AkDtnKK;;EAEE,gBAAA;EACA,2BAAA;ElDwnKP;AkDtnKK;;;EAGE,aAAA;EACA,2BAAA;EACA,uBAAA;ElDwnKP;AiD5hKD;EACE,eAAA;EACA,oBAAA;EjD8hKD;AiD5hKD;EACE,kBAAA;EACA,kBAAA;EjD8hKD;AmDlpKD;EACE,qBAAA;EACA,2BAAA;EACA,+BAAA;EACA,oBAAA;E9C0DA,mDAAA;EACQ,2CAAA;EL2lKT;AmDjpKD;EACE,eAAA;EnDmpKD;AmD9oKD;EACE,oBAAA;EACA,sCAAA;EvBpBA,8BAAA;EACC,6BAAA;E5BqqKF;AmDppKD;EAMI,gBAAA;EnDipKH;AmD5oKD;EACE,eAAA;EACA,kBAAA;EACA,iBAAA;EACA,gBAAA;EnD8oKD;AmDlpKD;;;;;EAWI,gBAAA;EnD8oKH;AmDzoKD;EACE,oBAAA;EACA,2BAAA;EACA,+BAAA;EvBxCA,iCAAA;EACC,gCAAA;E5BorKF;AmDnoKD;;EAGI,kBAAA;EnDooKH;AmDvoKD;;EAMM,qBAAA;EACA,kBAAA;EnDqoKL;AmDjoKG;;EAEI,eAAA;EvBvEN,8BAAA;EACC,6BAAA;E5B2sKF;AmDhoKG;;EAEI,kBAAA;EvBtEN,iCAAA;EACC,gCAAA;E5BysKF;AmD7nKD;EAEI,qBAAA;EnD8nKH;AmD3nKD;EACE,qBAAA;EnD6nKD;AmDrnKD;;;EAII,kBAAA;EnDsnKH;AmD1nKD;;;EAOM,oBAAA;EACA,qBAAA;EnDwnKL;AmDhoKD;;EvBnGE,8BAAA;EACC,6BAAA;E5BuuKF;AmDroKD;;;;EAmBQ,6BAAA;EACA,8BAAA;EnDwnKP;AmD5oKD;;;;;;;;EAwBU,6BAAA;EnD8nKT;AmDtpKD;;;;;;;;EA4BU,8BAAA;EnDooKT;AmDhqKD;;EvB3FE,iCAAA;EACC,gCAAA;E5B+vKF;AmDrqKD;;;;EAyCQ,gCAAA;EACA,iCAAA;EnDkoKP;AmD5qKD;;;;;;;;EA8CU,gCAAA;EnDwoKT;AmDtrKD;;;;;;;;EAkDU,iCAAA;EnD8oKT;AmDhsKD;;;;EA2DI,+BAAA;EnD2oKH;AmDtsKD;;EA+DI,eAAA;EnD2oKH;AmD1sKD;;EAmEI,WAAA;EnD2oKH;AmD9sKD;;;;;;;;;;;;EA0EU,gBAAA;EnDkpKT;AmD5tKD;;;;;;;;;;;;EA8EU,iBAAA;EnD4pKT;AmD1uKD;;;;;;;;EAuFU,kBAAA;EnD6pKT;AmDpvKD;;;;;;;;EAgGU,kBAAA;EnD8pKT;AmD9vKD;EAsGI,WAAA;EACA,kBAAA;EnD2pKH;AmDjpKD;EACE,qBAAA;EnDmpKD;AmDppKD;EAKI,kBAAA;EACA,oBAAA;EnDkpKH;AmDxpKD;EASM,iBAAA;EnDkpKL;AmD3pKD;EAcI,kBAAA;EnDgpKH;AmD9pKD;;EAkBM,+BAAA;EnDgpKL;AmDlqKD;EAuBI,eAAA;EnD8oKH;AmDrqKD;EAyBM,kCAAA;EnD+oKL;AmDxoKD;ECpPE,uBAAA;EpD+3KD;AoD73KC;EACE,gBAAA;EACA,2BAAA;EACA,uBAAA;EpD+3KH;AoDl4KC;EAMI,2BAAA;EpD+3KL;AoDr4KC;EASI,gBAAA;EACA,2BAAA;EpD+3KL;AoD53KC;EAEI,8BAAA;EpD63KL;AmDvpKD;ECvPE,uBAAA;EpDi5KD;AoD/4KC;EACE,gBAAA;EACA,2BAAA;EACA,uBAAA;EpDi5KH;AoDp5KC;EAMI,2BAAA;EpDi5KL;AoDv5KC;EASI,gBAAA;EACA,2BAAA;EpDi5KL;AoD94KC;EAEI,8BAAA;EpD+4KL;AmDtqKD;EC1PE,uBAAA;EpDm6KD;AoDj6KC;EACE,gBAAA;EACA,2BAAA;EACA,uBAAA;EpDm6KH;AoDt6KC;EAMI,2BAAA;EpDm6KL;AoDz6KC;EASI,gBAAA;EACA,2BAAA;EpDm6KL;AoDh6KC;EAEI,8BAAA;EpDi6KL;AmDrrKD;EC7PE,uBAAA;EpDq7KD;AoDn7KC;EACE,gBAAA;EACA,2BAAA;EACA,uBAAA;EpDq7KH;AoDx7KC;EAMI,2BAAA;EpDq7KL;AoD37KC;EASI,gBAAA;EACA,2BAAA;EpDq7KL;AoDl7KC;EAEI,8BAAA;EpDm7KL;AmDpsKD;EChQE,uBAAA;EpDu8KD;AoDr8KC;EACE,gBAAA;EACA,2BAAA;EACA,uBAAA;EpDu8KH;AoD18KC;EAMI,2BAAA;EpDu8KL;AoD78KC;EASI,gBAAA;EACA,2BAAA;EpDu8KL;AoDp8KC;EAEI,8BAAA;EpDq8KL;AmDntKD;ECnQE,uBAAA;EpDy9KD;AoDv9KC;EACE,gBAAA;EACA,2BAAA;EACA,uBAAA;EpDy9KH;AoD59KC;EAMI,2BAAA;EpDy9KL;AoD/9KC;EASI,gBAAA;EACA,2BAAA;EpDy9KL;AoDt9KC;EAEI,8BAAA;EpDu9KL;AqDv+KD;EACE,oBAAA;EACA,gBAAA;EACA,WAAA;EACA,YAAA;EACA,kBAAA;ErDy+KD;AqD9+KD;;;;;EAYI,oBAAA;EACA,QAAA;EACA,SAAA;EACA,WAAA;EACA,cAAA;EACA,aAAA;EACA,WAAA;ErDy+KH;AqDp+KD;EACE,wBAAA;ErDs+KD;AqDl+KD;EACE,qBAAA;ErDo+KD;AsD//KD;EACE,kBAAA;EACA,eAAA;EACA,qBAAA;EACA,2BAAA;EACA,2BAAA;EACA,oBAAA;EjDwDA,yDAAA;EACQ,iDAAA;EL08KT;AsDzgLD;EASI,oBAAA;EACA,mCAAA;EtDmgLH;AsD9/KD;EACE,eAAA;EACA,oBAAA;EtDggLD;AsD9/KD;EACE,cAAA;EACA,oBAAA;EtDggLD;AuDthLD;EACE,cAAA;EACA,iBAAA;EACA,mBAAA;EACA,gBAAA;EACA,gBAAA;EACA,8BAAA;EjCRA,cAAA;EAGA,2BAAA;EtB+hLD;AuDvhLC;;EAEE,gBAAA;EACA,uBAAA;EACA,iBAAA;EjCfF,cAAA;EAGA,2BAAA;EtBuiLD;AuDnhLC;EACE,YAAA;EACA,iBAAA;EACA,yBAAA;EACA,WAAA;EACA,0BAAA;EvDqhLH;AwD1iLD;EACE,kBAAA;ExD4iLD;AwDxiLD;EACE,eAAA;EACA,kBAAA;EACA,iBAAA;EACA,QAAA;EACA,UAAA;EACA,WAAA;EACA,SAAA;EACA,eAAA;EACA,mCAAA;EAIA,YAAA;ExDuiLD;AwDpiLC;EnD+GA,uCAAA;EACI,mCAAA;EACC,kCAAA;EACG,+BAAA;EAkER,qDAAA;EAEK,2CAAA;EACG,qCAAA;ELu3KT;AwD1iLC;EnD2GA,oCAAA;EACI,gCAAA;EACC,+BAAA;EACG,4BAAA;ELk8KT;AwD9iLD;EACE,oBAAA;EACA,kBAAA;ExDgjLD;AwD5iLD;EACE,oBAAA;EACA,aAAA;EACA,cAAA;ExD8iLD;AwD1iLD;EACE,oBAAA;EACA,2BAAA;EACA,2BAAA;EACA,sCAAA;EACA,oBAAA;EnDaA,kDAAA;EACQ,0CAAA;EmDZR,sCAAA;UAAA,8BAAA;EAEA,YAAA;ExD4iLD;AwDxiLD;EACE,iBAAA;EACA,QAAA;EACA,UAAA;EACA,WAAA;EACA,SAAA;EACA,eAAA;EACA,2BAAA;ExD0iLD;AwDxiLC;ElCrEA,YAAA;EAGA,0BAAA;EtB8mLD;AwD3iLC;ElCtEA,cAAA;EAGA,2BAAA;EtBknLD;AwD1iLD;EACE,eAAA;EACA,kCAAA;EACA,2BAAA;ExD4iLD;AwDziLD;EACE,kBAAA;ExD2iLD;AwDviLD;EACE,WAAA;EACA,yBAAA;ExDyiLD;AwDpiLD;EACE,oBAAA;EACA,eAAA;ExDsiLD;AwDliLD;EACE,eAAA;EACA,mBAAA;EACA,+BAAA;ExDoiLD;AwDviLD;EAQI,kBAAA;EACA,kBAAA;ExDkiLH;AwD3iLD;EAaI,mBAAA;ExDiiLH;AwD9iLD;EAiBI,gBAAA;ExDgiLH;AwD3hLD;EACE,oBAAA;EACA,cAAA;EACA,aAAA;EACA,cAAA;EACA,kBAAA;ExD6hLD;AwD3gLD;EAZE;IACE,cAAA;IACA,mBAAA;IxD0hLD;EwDxhLD;InDvEA,mDAAA;IACQ,2CAAA;ILkmLP;EwDvhLD;IAAY,cAAA;IxD0hLX;EACF;AwDrhLD;EAFE;IAAY,cAAA;IxD2hLX;EACF;AyD1qLD;EACE,oBAAA;EACA,eAAA;EACA,gBAAA;EAEA,6DAAA;EACA,iBAAA;EACA,qBAAA;EACA,kBAAA;EnCXA,YAAA;EAGA,0BAAA;EtBqrLD;AyD1qLC;EnCdA,cAAA;EAGA,2BAAA;EtByrLD;AyD7qLC;EAAW,kBAAA;EAAmB,gBAAA;EzDirL/B;AyDhrLC;EAAW,kBAAA;EAAmB,gBAAA;EzDorL/B;AyDnrLC;EAAW,iBAAA;EAAmB,gBAAA;EzDurL/B;AyDtrLC;EAAW,mBAAA;EAAmB,gBAAA;EzD0rL/B;AyDtrLD;EACE,kBAAA;EACA,kBAAA;EACA,gBAAA;EACA,oBAAA;EACA,uBAAA;EACA,2BAAA;EACA,oBAAA;EzDwrLD;AyDprLD;EACE,oBAAA;EACA,UAAA;EACA,WAAA;EACA,2BAAA;EACA,qBAAA;EzDsrLD;AyDlrLC;EACE,WAAA;EACA,WAAA;EACA,mBAAA;EACA,yBAAA;EACA,2BAAA;EzDorLH;AyDlrLC;EACE,WAAA;EACA,YAAA;EACA,qBAAA;EACA,yBAAA;EACA,2BAAA;EzDorLH;AyDlrLC;EACE,WAAA;EACA,WAAA;EACA,qBAAA;EACA,yBAAA;EACA,2BAAA;EzDorLH;AyDlrLC;EACE,UAAA;EACA,SAAA;EACA,kBAAA;EACA,6BAAA;EACA,6BAAA;EzDorLH;AyDlrLC;EACE,UAAA;EACA,UAAA;EACA,kBAAA;EACA,6BAAA;EACA,4BAAA;EzDorLH;AyDlrLC;EACE,QAAA;EACA,WAAA;EACA,mBAAA;EACA,yBAAA;EACA,8BAAA;EzDorLH;AyDlrLC;EACE,QAAA;EACA,YAAA;EACA,kBAAA;EACA,yBAAA;EACA,8BAAA;EzDorLH;AyDlrLC;EACE,QAAA;EACA,WAAA;EACA,kBAAA;EACA,yBAAA;EACA,8BAAA;EzDorLH;A0DlxLD;EACE,oBAAA;EACA,QAAA;EACA,SAAA;EACA,eAAA;EACA,eAAA;EACA,kBAAA;EACA,cAAA;EAEA,6DAAA;EACA,iBAAA;EACA,qBAAA;EACA,yBAAA;EACA,kBAAA;EACA,2BAAA;EACA,sCAAA;UAAA,8BAAA;EACA,2BAAA;EACA,sCAAA;EACA,oBAAA;ErD6CA,mDAAA;EACQ,2CAAA;EqD1CR,qBAAA;E1DkxLD;A0D/wLC;EAAY,mBAAA;E1DkxLb;A0DjxLC;EAAY,mBAAA;E1DoxLb;A0DnxLC;EAAY,kBAAA;E1DsxLb;A0DrxLC;EAAY,oBAAA;E1DwxLb;A0DrxLD;EACE,WAAA;EACA,mBAAA;EACA,iBAAA;EACA,2BAAA;EACA,kCAAA;EACA,4BAAA;E1DuxLD;A0DpxLD;EACE,mBAAA;E1DsxLD;A0D9wLC;;EAEE,oBAAA;EACA,gBAAA;EACA,UAAA;EACA,WAAA;EACA,2BAAA;EACA,qBAAA;E1DgxLH;A0D7wLD;EACE,oBAAA;E1D+wLD;A0D7wLD;EACE,oBAAA;EACA,aAAA;E1D+wLD;A0D3wLC;EACE,WAAA;EACA,oBAAA;EACA,wBAAA;EACA,2BAAA;EACA,uCAAA;EACA,eAAA;E1D6wLH;A0D5wLG;EACE,cAAA;EACA,aAAA;EACA,oBAAA;EACA,wBAAA;EACA,2BAAA;E1D8wLL;A0D3wLC;EACE,UAAA;EACA,aAAA;EACA,mBAAA;EACA,sBAAA;EACA,6BAAA;EACA,yCAAA;E1D6wLH;A0D5wLG;EACE,cAAA;EACA,WAAA;EACA,eAAA;EACA,sBAAA;EACA,6BAAA;E1D8wLL;A0D3wLC;EACE,WAAA;EACA,oBAAA;EACA,qBAAA;EACA,8BAAA;EACA,0CAAA;EACA,YAAA;E1D6wLH;A0D5wLG;EACE,cAAA;EACA,UAAA;EACA,oBAAA;EACA,qBAAA;EACA,8BAAA;E1D8wLL;A0D1wLC;EACE,UAAA;EACA,cAAA;EACA,mBAAA;EACA,uBAAA;EACA,4BAAA;EACA,wCAAA;E1D4wLH;A0D3wLG;EACE,cAAA;EACA,YAAA;EACA,uBAAA;EACA,4BAAA;EACA,eAAA;E1D6wLL;A2D14LD;EACE,oBAAA;E3D44LD;A2Dz4LD;EACE,oBAAA;EACA,kBAAA;EACA,aAAA;E3D24LD;A2D94LD;EAMI,eAAA;EACA,oBAAA;EtD6KF,2CAAA;EACK,sCAAA;EACG,mCAAA;EL+tLT;A2Dr5LD;;EAcM,gBAAA;E3D24LL;A2Dj3LC;EAAA;ItDiKA,wDAAA;IAEK,8CAAA;IACG,wCAAA;IA7JR,qCAAA;IAEQ,6BAAA;IA+GR,2BAAA;IAEQ,mBAAA;ILowLP;E2D/4LG;;ItDmHJ,4CAAA;IACQ,oCAAA;IsDjHF,SAAA;I3Dk5LL;E2Dh5LG;;ItD8GJ,6CAAA;IACQ,qCAAA;IsD5GF,SAAA;I3Dm5LL;E2Dj5LG;;;ItDyGJ,yCAAA;IACQ,iCAAA;IsDtGF,SAAA;I3Do5LL;EACF;A2D17LD;;;EA6CI,gBAAA;E3Dk5LH;A2D/7LD;EAiDI,SAAA;E3Di5LH;A2Dl8LD;;EAsDI,oBAAA;EACA,QAAA;EACA,aAAA;E3Dg5LH;A2Dx8LD;EA4DI,YAAA;E3D+4LH;A2D38LD;EA+DI,aAAA;E3D+4LH;A2D98LD;;EAmEI,SAAA;E3D+4LH;A2Dl9LD;EAuEI,aAAA;E3D84LH;A2Dr9LD;EA0EI,YAAA;E3D84LH;A2Dt4LD;EACE,oBAAA;EACA,QAAA;EACA,SAAA;EACA,WAAA;EACA,YAAA;ErC9FA,cAAA;EAGA,2BAAA;EqC6FA,iBAAA;EACA,gBAAA;EACA,oBAAA;EACA,2CAAA;E3Dy4LD;A2Dp4LC;EblGE,oGAAA;EACA,+FAAA;EACA,sHAAA;EAAA,gGAAA;EACA,6BAAA;EACA,wHAAA;E9Cy+LH;A2Dx4LC;EACE,YAAA;EACA,UAAA;EbvGA,oGAAA;EACA,+FAAA;EACA,sHAAA;EAAA,gGAAA;EACA,6BAAA;EACA,wHAAA;E9Ck/LH;A2D14LC;;EAEE,YAAA;EACA,gBAAA;EACA,uBAAA;ErCtHF,cAAA;EAGA,2BAAA;EtBigMD;A2D36LD;;;;EAsCI,oBAAA;EACA,UAAA;EACA,YAAA;EACA,uBAAA;E3D24LH;A2Dp7LD;;EA6CI,WAAA;EACA,oBAAA;E3D24LH;A2Dz7LD;;EAkDI,YAAA;EACA,qBAAA;E3D24LH;A2D97LD;;EAuDI,aAAA;EACA,cAAA;EACA,mBAAA;EACA,gBAAA;EACA,oBAAA;E3D24LH;A2Dt4LG;EACE,kBAAA;E3Dw4LL;A2Dp4LG;EACE,kBAAA;E3Ds4LL;A2D53LD;EACE,oBAAA;EACA,cAAA;EACA,WAAA;EACA,aAAA;EACA,YAAA;EACA,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,oBAAA;E3D83LD;A2Dv4LD;EAYI,uBAAA;EACA,aAAA;EACA,cAAA;EACA,aAAA;EACA,qBAAA;EACA,2BAAA;EACA,qBAAA;EACA,iBAAA;EAWA,2BAAA;EACA,oCAAA;E3Do3LH;A2Dn5LD;EAkCI,WAAA;EACA,aAAA;EACA,cAAA;EACA,2BAAA;E3Do3LH;A2D72LD;EACE,oBAAA;EACA,WAAA;EACA,YAAA;EACA,cAAA;EACA,aAAA;EACA,mBAAA;EACA,sBAAA;EACA,gBAAA;EACA,oBAAA;EACA,2CAAA;E3D+2LD;A2D92LC;EACE,mBAAA;E3Dg3LH;A2Dv0LD;EAhCE;;;;IAKI,aAAA;IACA,cAAA;IACA,mBAAA;IACA,iBAAA;I3Dy2LH;E2Dj3LD;;IAYI,oBAAA;I3Dy2LH;E2Dr3LD;;IAgBI,qBAAA;I3Dy2LH;E2Dp2LD;IACE,WAAA;IACA,YAAA;IACA,sBAAA;I3Ds2LD;E2Dl2LD;IACE,cAAA;I3Do2LD;EACF;A4DlmMC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAEE,cAAA;EACA,gBAAA;E5DgoMH;A4D9nMC;;;;;;;;;;;;;;;EACE,aAAA;E5D8oMH;AiCtpMD;E4BRE,gBAAA;EACA,mBAAA;EACA,oBAAA;E7DiqMD;AiCxpMD;EACE,yBAAA;EjC0pMD;AiCxpMD;EACE,wBAAA;EjC0pMD;AiClpMD;EACE,0BAAA;EjCopMD;AiClpMD;EACE,2BAAA;EjCopMD;AiClpMD;EACE,oBAAA;EjCopMD;AiClpMD;E6BzBE,aAAA;EACA,oBAAA;EACA,mBAAA;EACA,+BAAA;EACA,WAAA;E9D8qMD;AiChpMD;EACE,0BAAA;EjCkpMD;AiC3oMD;EACE,iBAAA;EjC6oMD;A+D9qMD;EACE,qBAAA;E/DgrMD;A+D1qMD;;;;ECdE,0BAAA;EhE8rMD;A+DzqMD;;;;;;;;;;;;EAYE,0BAAA;E/D2qMD;A+DpqMD;EAAA;IChDE,2BAAA;IhEwtMC;EgEvtMD;IAAU,gBAAA;IhE0tMT;EgEztMD;IAAU,+BAAA;IhE4tMT;EgE3tMD;;IACU,gCAAA;IhE8tMT;EACF;A+D9qMD;EAAA;IAFI,2BAAA;I/DorMD;EACF;A+D9qMD;EAAA;IAFI,4BAAA;I/DorMD;EACF;A+D9qMD;EAAA;IAFI,kCAAA;I/DorMD;EACF;A+D7qMD;EAAA;ICrEE,2BAAA;IhEsvMC;EgErvMD;IAAU,gBAAA;IhEwvMT;EgEvvMD;IAAU,+BAAA;IhE0vMT;EgEzvMD;;IACU,gCAAA;IhE4vMT;EACF;A+DvrMD;EAAA;IAFI,2BAAA;I/D6rMD;EACF;A+DvrMD;EAAA;IAFI,4BAAA;I/D6rMD;EACF;A+DvrMD;EAAA;IAFI,kCAAA;I/D6rMD;EACF;A+DtrMD;EAAA;IC1FE,2BAAA;IhEoxMC;EgEnxMD;IAAU,gBAAA;IhEsxMT;EgErxMD;IAAU,+BAAA;IhEwxMT;EgEvxMD;;IACU,gCAAA;IhE0xMT;EACF;A+DhsMD;EAAA;IAFI,2BAAA;I/DssMD;EACF;A+DhsMD;EAAA;IAFI,4BAAA;I/DssMD;EACF;A+DhsMD;EAAA;IAFI,kCAAA;I/DssMD;EACF;A+D/rMD;EAAA;IC/GE,2BAAA;IhEkzMC;EgEjzMD;IAAU,gBAAA;IhEozMT;EgEnzMD;IAAU,+BAAA;IhEszMT;EgErzMD;;IACU,gCAAA;IhEwzMT;EACF;A+DzsMD;EAAA;IAFI,2BAAA;I/D+sMD;EACF;A+DzsMD;EAAA;IAFI,4BAAA;I/D+sMD;EACF;A+DzsMD;EAAA;IAFI,kCAAA;I/D+sMD;EACF;A+DxsMD;EAAA;IC5HE,0BAAA;IhEw0MC;EACF;A+DxsMD;EAAA;ICjIE,0BAAA;IhE60MC;EACF;A+DxsMD;EAAA;ICtIE,0BAAA;IhEk1MC;EACF;A+DxsMD;EAAA;IC3IE,0BAAA;IhEu1MC;EACF;A+DrsMD;ECnJE,0BAAA;EhE21MD;A+DlsMD;EAAA;ICjKE,2BAAA;IhEu2MC;EgEt2MD;IAAU,gBAAA;IhEy2MT;EgEx2MD;IAAU,+BAAA;IhE22MT;EgE12MD;;IACU,gCAAA;IhE62MT;EACF;A+DhtMD;EACE,0BAAA;E/DktMD;A+D7sMD;EAAA;IAFI,2BAAA;I/DmtMD;EACF;A+DjtMD;EACE,0BAAA;E/DmtMD;A+D9sMD;EAAA;IAFI,4BAAA;I/DotMD;EACF;A+DltMD;EACE,0BAAA;E/DotMD;A+D/sMD;EAAA;IAFI,kCAAA;I/DqtMD;EACF;A+D9sMD;EAAA;ICpLE,0BAAA;IhEs4MC;EACF","file":"bootstrap.css","sourcesContent":["/*! normalize.css v3.0.2 | MIT License | git.io/normalize */\nhtml {\n font-family: sans-serif;\n -ms-text-size-adjust: 100%;\n -webkit-text-size-adjust: 100%;\n}\nbody {\n margin: 0;\n}\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n display: block;\n}\naudio,\ncanvas,\nprogress,\nvideo {\n display: inline-block;\n vertical-align: baseline;\n}\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n[hidden],\ntemplate {\n display: none;\n}\na {\n background-color: transparent;\n}\na:active,\na:hover {\n outline: 0;\n}\nabbr[title] {\n border-bottom: 1px dotted;\n}\nb,\nstrong {\n font-weight: bold;\n}\ndfn {\n font-style: italic;\n}\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\nmark {\n background: #ff0;\n color: #000;\n}\nsmall {\n font-size: 80%;\n}\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\nsup {\n top: -0.5em;\n}\nsub {\n bottom: -0.25em;\n}\nimg {\n border: 0;\n}\nsvg:not(:root) {\n overflow: hidden;\n}\nfigure {\n margin: 1em 40px;\n}\nhr {\n -moz-box-sizing: content-box;\n box-sizing: content-box;\n height: 0;\n}\npre {\n overflow: auto;\n}\ncode,\nkbd,\npre,\nsamp {\n font-family: monospace, monospace;\n font-size: 1em;\n}\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n color: inherit;\n font: inherit;\n margin: 0;\n}\nbutton {\n overflow: visible;\n}\nbutton,\nselect {\n text-transform: none;\n}\nbutton,\nhtml input[type=\"button\"],\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n -webkit-appearance: button;\n cursor: pointer;\n}\nbutton[disabled],\nhtml input[disabled] {\n cursor: default;\n}\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n border: 0;\n padding: 0;\n}\ninput {\n line-height: normal;\n}\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n box-sizing: border-box;\n padding: 0;\n}\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\ninput[type=\"search\"] {\n -webkit-appearance: textfield;\n -moz-box-sizing: content-box;\n -webkit-box-sizing: content-box;\n box-sizing: content-box;\n}\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\nfieldset {\n border: 1px solid #c0c0c0;\n margin: 0 2px;\n padding: 0.35em 0.625em 0.75em;\n}\nlegend {\n border: 0;\n padding: 0;\n}\ntextarea {\n overflow: auto;\n}\noptgroup {\n font-weight: bold;\n}\ntable {\n border-collapse: collapse;\n border-spacing: 0;\n}\ntd,\nth {\n padding: 0;\n}\n/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n@media print {\n *,\n *:before,\n *:after {\n background: transparent !important;\n color: #000 !important;\n box-shadow: none !important;\n text-shadow: none !important;\n }\n a,\n a:visited {\n text-decoration: underline;\n }\n a[href]:after {\n content: \" (\" attr(href) \")\";\n }\n abbr[title]:after {\n content: \" (\" attr(title) \")\";\n }\n a[href^=\"#\"]:after,\n a[href^=\"javascript:\"]:after {\n content: \"\";\n }\n pre,\n blockquote {\n border: 1px solid #999;\n page-break-inside: avoid;\n }\n thead {\n display: table-header-group;\n }\n tr,\n img {\n page-break-inside: avoid;\n }\n img {\n max-width: 100% !important;\n }\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n h2,\n h3 {\n page-break-after: avoid;\n }\n select {\n background: #fff !important;\n }\n .navbar {\n display: none;\n }\n .btn > .caret,\n .dropup > .btn > .caret {\n border-top-color: #000 !important;\n }\n .label {\n border: 1px solid #000;\n }\n .table {\n border-collapse: collapse !important;\n }\n .table td,\n .table th {\n background-color: #fff !important;\n }\n .table-bordered th,\n .table-bordered td {\n border: 1px solid #ddd !important;\n }\n}\n@font-face {\n font-family: 'Glyphicons Halflings';\n src: url('../fonts/glyphicons-halflings-regular.eot');\n src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');\n}\n.glyphicon {\n position: relative;\n top: 1px;\n display: inline-block;\n font-family: 'Glyphicons Halflings';\n font-style: normal;\n font-weight: normal;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n.glyphicon-asterisk:before {\n content: \"\\2a\";\n}\n.glyphicon-plus:before {\n content: \"\\2b\";\n}\n.glyphicon-euro:before,\n.glyphicon-eur:before {\n content: \"\\20ac\";\n}\n.glyphicon-minus:before {\n content: \"\\2212\";\n}\n.glyphicon-cloud:before {\n content: \"\\2601\";\n}\n.glyphicon-envelope:before {\n content: \"\\2709\";\n}\n.glyphicon-pencil:before {\n content: \"\\270f\";\n}\n.glyphicon-glass:before {\n content: \"\\e001\";\n}\n.glyphicon-music:before {\n content: \"\\e002\";\n}\n.glyphicon-search:before {\n content: \"\\e003\";\n}\n.glyphicon-heart:before {\n content: \"\\e005\";\n}\n.glyphicon-star:before {\n content: \"\\e006\";\n}\n.glyphicon-star-empty:before {\n content: \"\\e007\";\n}\n.glyphicon-user:before {\n content: \"\\e008\";\n}\n.glyphicon-film:before {\n content: \"\\e009\";\n}\n.glyphicon-th-large:before {\n content: \"\\e010\";\n}\n.glyphicon-th:before {\n content: \"\\e011\";\n}\n.glyphicon-th-list:before {\n content: \"\\e012\";\n}\n.glyphicon-ok:before {\n content: \"\\e013\";\n}\n.glyphicon-remove:before {\n content: \"\\e014\";\n}\n.glyphicon-zoom-in:before {\n content: \"\\e015\";\n}\n.glyphicon-zoom-out:before {\n content: \"\\e016\";\n}\n.glyphicon-off:before {\n content: \"\\e017\";\n}\n.glyphicon-signal:before {\n content: \"\\e018\";\n}\n.glyphicon-cog:before {\n content: \"\\e019\";\n}\n.glyphicon-trash:before {\n content: \"\\e020\";\n}\n.glyphicon-home:before {\n content: \"\\e021\";\n}\n.glyphicon-file:before {\n content: \"\\e022\";\n}\n.glyphicon-time:before {\n content: \"\\e023\";\n}\n.glyphicon-road:before {\n content: \"\\e024\";\n}\n.glyphicon-download-alt:before {\n content: \"\\e025\";\n}\n.glyphicon-download:before {\n content: \"\\e026\";\n}\n.glyphicon-upload:before {\n content: \"\\e027\";\n}\n.glyphicon-inbox:before {\n content: \"\\e028\";\n}\n.glyphicon-play-circle:before {\n content: \"\\e029\";\n}\n.glyphicon-repeat:before {\n content: \"\\e030\";\n}\n.glyphicon-refresh:before {\n content: \"\\e031\";\n}\n.glyphicon-list-alt:before {\n content: \"\\e032\";\n}\n.glyphicon-lock:before {\n content: \"\\e033\";\n}\n.glyphicon-flag:before {\n content: \"\\e034\";\n}\n.glyphicon-headphones:before {\n content: \"\\e035\";\n}\n.glyphicon-volume-off:before {\n content: \"\\e036\";\n}\n.glyphicon-volume-down:before {\n content: \"\\e037\";\n}\n.glyphicon-volume-up:before {\n content: \"\\e038\";\n}\n.glyphicon-qrcode:before {\n content: \"\\e039\";\n}\n.glyphicon-barcode:before {\n content: \"\\e040\";\n}\n.glyphicon-tag:before {\n content: \"\\e041\";\n}\n.glyphicon-tags:before {\n content: \"\\e042\";\n}\n.glyphicon-book:before {\n content: \"\\e043\";\n}\n.glyphicon-bookmark:before {\n content: \"\\e044\";\n}\n.glyphicon-print:before {\n content: \"\\e045\";\n}\n.glyphicon-camera:before {\n content: \"\\e046\";\n}\n.glyphicon-font:before {\n content: \"\\e047\";\n}\n.glyphicon-bold:before {\n content: \"\\e048\";\n}\n.glyphicon-italic:before {\n content: \"\\e049\";\n}\n.glyphicon-text-height:before {\n content: \"\\e050\";\n}\n.glyphicon-text-width:before {\n content: \"\\e051\";\n}\n.glyphicon-align-left:before {\n content: \"\\e052\";\n}\n.glyphicon-align-center:before {\n content: \"\\e053\";\n}\n.glyphicon-align-right:before {\n content: \"\\e054\";\n}\n.glyphicon-align-justify:before {\n content: \"\\e055\";\n}\n.glyphicon-list:before {\n content: \"\\e056\";\n}\n.glyphicon-indent-left:before {\n content: \"\\e057\";\n}\n.glyphicon-indent-right:before {\n content: \"\\e058\";\n}\n.glyphicon-facetime-video:before {\n content: \"\\e059\";\n}\n.glyphicon-picture:before {\n content: \"\\e060\";\n}\n.glyphicon-map-marker:before {\n content: \"\\e062\";\n}\n.glyphicon-adjust:before {\n content: \"\\e063\";\n}\n.glyphicon-tint:before {\n content: \"\\e064\";\n}\n.glyphicon-edit:before {\n content: \"\\e065\";\n}\n.glyphicon-share:before {\n content: \"\\e066\";\n}\n.glyphicon-check:before {\n content: \"\\e067\";\n}\n.glyphicon-move:before {\n content: \"\\e068\";\n}\n.glyphicon-step-backward:before {\n content: \"\\e069\";\n}\n.glyphicon-fast-backward:before {\n content: \"\\e070\";\n}\n.glyphicon-backward:before {\n content: \"\\e071\";\n}\n.glyphicon-play:before {\n content: \"\\e072\";\n}\n.glyphicon-pause:before {\n content: \"\\e073\";\n}\n.glyphicon-stop:before {\n content: \"\\e074\";\n}\n.glyphicon-forward:before {\n content: \"\\e075\";\n}\n.glyphicon-fast-forward:before {\n content: \"\\e076\";\n}\n.glyphicon-step-forward:before {\n content: \"\\e077\";\n}\n.glyphicon-eject:before {\n content: \"\\e078\";\n}\n.glyphicon-chevron-left:before {\n content: \"\\e079\";\n}\n.glyphicon-chevron-right:before {\n content: \"\\e080\";\n}\n.glyphicon-plus-sign:before {\n content: \"\\e081\";\n}\n.glyphicon-minus-sign:before {\n content: \"\\e082\";\n}\n.glyphicon-remove-sign:before {\n content: \"\\e083\";\n}\n.glyphicon-ok-sign:before {\n content: \"\\e084\";\n}\n.glyphicon-question-sign:before {\n content: \"\\e085\";\n}\n.glyphicon-info-sign:before {\n content: \"\\e086\";\n}\n.glyphicon-screenshot:before {\n content: \"\\e087\";\n}\n.glyphicon-remove-circle:before {\n content: \"\\e088\";\n}\n.glyphicon-ok-circle:before {\n content: \"\\e089\";\n}\n.glyphicon-ban-circle:before {\n content: \"\\e090\";\n}\n.glyphicon-arrow-left:before {\n content: \"\\e091\";\n}\n.glyphicon-arrow-right:before {\n content: \"\\e092\";\n}\n.glyphicon-arrow-up:before {\n content: \"\\e093\";\n}\n.glyphicon-arrow-down:before {\n content: \"\\e094\";\n}\n.glyphicon-share-alt:before {\n content: \"\\e095\";\n}\n.glyphicon-resize-full:before {\n content: \"\\e096\";\n}\n.glyphicon-resize-small:before {\n content: \"\\e097\";\n}\n.glyphicon-exclamation-sign:before {\n content: \"\\e101\";\n}\n.glyphicon-gift:before {\n content: \"\\e102\";\n}\n.glyphicon-leaf:before {\n content: \"\\e103\";\n}\n.glyphicon-fire:before {\n content: \"\\e104\";\n}\n.glyphicon-eye-open:before {\n content: \"\\e105\";\n}\n.glyphicon-eye-close:before {\n content: \"\\e106\";\n}\n.glyphicon-warning-sign:before {\n content: \"\\e107\";\n}\n.glyphicon-plane:before {\n content: \"\\e108\";\n}\n.glyphicon-calendar:before {\n content: \"\\e109\";\n}\n.glyphicon-random:before {\n content: \"\\e110\";\n}\n.glyphicon-comment:before {\n content: \"\\e111\";\n}\n.glyphicon-magnet:before {\n content: \"\\e112\";\n}\n.glyphicon-chevron-up:before {\n content: \"\\e113\";\n}\n.glyphicon-chevron-down:before {\n content: \"\\e114\";\n}\n.glyphicon-retweet:before {\n content: \"\\e115\";\n}\n.glyphicon-shopping-cart:before {\n content: \"\\e116\";\n}\n.glyphicon-folder-close:before {\n content: \"\\e117\";\n}\n.glyphicon-folder-open:before {\n content: \"\\e118\";\n}\n.glyphicon-resize-vertical:before {\n content: \"\\e119\";\n}\n.glyphicon-resize-horizontal:before {\n content: \"\\e120\";\n}\n.glyphicon-hdd:before {\n content: \"\\e121\";\n}\n.glyphicon-bullhorn:before {\n content: \"\\e122\";\n}\n.glyphicon-bell:before {\n content: \"\\e123\";\n}\n.glyphicon-certificate:before {\n content: \"\\e124\";\n}\n.glyphicon-thumbs-up:before {\n content: \"\\e125\";\n}\n.glyphicon-thumbs-down:before {\n content: \"\\e126\";\n}\n.glyphicon-hand-right:before {\n content: \"\\e127\";\n}\n.glyphicon-hand-left:before {\n content: \"\\e128\";\n}\n.glyphicon-hand-up:before {\n content: \"\\e129\";\n}\n.glyphicon-hand-down:before {\n content: \"\\e130\";\n}\n.glyphicon-circle-arrow-right:before {\n content: \"\\e131\";\n}\n.glyphicon-circle-arrow-left:before {\n content: \"\\e132\";\n}\n.glyphicon-circle-arrow-up:before {\n content: \"\\e133\";\n}\n.glyphicon-circle-arrow-down:before {\n content: \"\\e134\";\n}\n.glyphicon-globe:before {\n content: \"\\e135\";\n}\n.glyphicon-wrench:before {\n content: \"\\e136\";\n}\n.glyphicon-tasks:before {\n content: \"\\e137\";\n}\n.glyphicon-filter:before {\n content: \"\\e138\";\n}\n.glyphicon-briefcase:before {\n content: \"\\e139\";\n}\n.glyphicon-fullscreen:before {\n content: \"\\e140\";\n}\n.glyphicon-dashboard:before {\n content: \"\\e141\";\n}\n.glyphicon-paperclip:before {\n content: \"\\e142\";\n}\n.glyphicon-heart-empty:before {\n content: \"\\e143\";\n}\n.glyphicon-link:before {\n content: \"\\e144\";\n}\n.glyphicon-phone:before {\n content: \"\\e145\";\n}\n.glyphicon-pushpin:before {\n content: \"\\e146\";\n}\n.glyphicon-usd:before {\n content: \"\\e148\";\n}\n.glyphicon-gbp:before {\n content: \"\\e149\";\n}\n.glyphicon-sort:before {\n content: \"\\e150\";\n}\n.glyphicon-sort-by-alphabet:before {\n content: \"\\e151\";\n}\n.glyphicon-sort-by-alphabet-alt:before {\n content: \"\\e152\";\n}\n.glyphicon-sort-by-order:before {\n content: \"\\e153\";\n}\n.glyphicon-sort-by-order-alt:before {\n content: \"\\e154\";\n}\n.glyphicon-sort-by-attributes:before {\n content: \"\\e155\";\n}\n.glyphicon-sort-by-attributes-alt:before {\n content: \"\\e156\";\n}\n.glyphicon-unchecked:before {\n content: \"\\e157\";\n}\n.glyphicon-expand:before {\n content: \"\\e158\";\n}\n.glyphicon-collapse-down:before {\n content: \"\\e159\";\n}\n.glyphicon-collapse-up:before {\n content: \"\\e160\";\n}\n.glyphicon-log-in:before {\n content: \"\\e161\";\n}\n.glyphicon-flash:before {\n content: \"\\e162\";\n}\n.glyphicon-log-out:before {\n content: \"\\e163\";\n}\n.glyphicon-new-window:before {\n content: \"\\e164\";\n}\n.glyphicon-record:before {\n content: \"\\e165\";\n}\n.glyphicon-save:before {\n content: \"\\e166\";\n}\n.glyphicon-open:before {\n content: \"\\e167\";\n}\n.glyphicon-saved:before {\n content: \"\\e168\";\n}\n.glyphicon-import:before {\n content: \"\\e169\";\n}\n.glyphicon-export:before {\n content: \"\\e170\";\n}\n.glyphicon-send:before {\n content: \"\\e171\";\n}\n.glyphicon-floppy-disk:before {\n content: \"\\e172\";\n}\n.glyphicon-floppy-saved:before {\n content: \"\\e173\";\n}\n.glyphicon-floppy-remove:before {\n content: \"\\e174\";\n}\n.glyphicon-floppy-save:before {\n content: \"\\e175\";\n}\n.glyphicon-floppy-open:before {\n content: \"\\e176\";\n}\n.glyphicon-credit-card:before {\n content: \"\\e177\";\n}\n.glyphicon-transfer:before {\n content: \"\\e178\";\n}\n.glyphicon-cutlery:before {\n content: \"\\e179\";\n}\n.glyphicon-header:before {\n content: \"\\e180\";\n}\n.glyphicon-compressed:before {\n content: \"\\e181\";\n}\n.glyphicon-earphone:before {\n content: \"\\e182\";\n}\n.glyphicon-phone-alt:before {\n content: \"\\e183\";\n}\n.glyphicon-tower:before {\n content: \"\\e184\";\n}\n.glyphicon-stats:before {\n content: \"\\e185\";\n}\n.glyphicon-sd-video:before {\n content: \"\\e186\";\n}\n.glyphicon-hd-video:before {\n content: \"\\e187\";\n}\n.glyphicon-subtitles:before {\n content: \"\\e188\";\n}\n.glyphicon-sound-stereo:before {\n content: \"\\e189\";\n}\n.glyphicon-sound-dolby:before {\n content: \"\\e190\";\n}\n.glyphicon-sound-5-1:before {\n content: \"\\e191\";\n}\n.glyphicon-sound-6-1:before {\n content: \"\\e192\";\n}\n.glyphicon-sound-7-1:before {\n content: \"\\e193\";\n}\n.glyphicon-copyright-mark:before {\n content: \"\\e194\";\n}\n.glyphicon-registration-mark:before {\n content: \"\\e195\";\n}\n.glyphicon-cloud-download:before {\n content: \"\\e197\";\n}\n.glyphicon-cloud-upload:before {\n content: \"\\e198\";\n}\n.glyphicon-tree-conifer:before {\n content: \"\\e199\";\n}\n.glyphicon-tree-deciduous:before {\n content: \"\\e200\";\n}\n.glyphicon-cd:before {\n content: \"\\e201\";\n}\n.glyphicon-save-file:before {\n content: \"\\e202\";\n}\n.glyphicon-open-file:before {\n content: \"\\e203\";\n}\n.glyphicon-level-up:before {\n content: \"\\e204\";\n}\n.glyphicon-copy:before {\n content: \"\\e205\";\n}\n.glyphicon-paste:before {\n content: \"\\e206\";\n}\n.glyphicon-alert:before {\n content: \"\\e209\";\n}\n.glyphicon-equalizer:before {\n content: \"\\e210\";\n}\n.glyphicon-king:before {\n content: \"\\e211\";\n}\n.glyphicon-queen:before {\n content: \"\\e212\";\n}\n.glyphicon-pawn:before {\n content: \"\\e213\";\n}\n.glyphicon-bishop:before {\n content: \"\\e214\";\n}\n.glyphicon-knight:before {\n content: \"\\e215\";\n}\n.glyphicon-baby-formula:before {\n content: \"\\e216\";\n}\n.glyphicon-tent:before {\n content: \"\\26fa\";\n}\n.glyphicon-blackboard:before {\n content: \"\\e218\";\n}\n.glyphicon-bed:before {\n content: \"\\e219\";\n}\n.glyphicon-apple:before {\n content: \"\\f8ff\";\n}\n.glyphicon-erase:before {\n content: \"\\e221\";\n}\n.glyphicon-hourglass:before {\n content: \"\\231b\";\n}\n.glyphicon-lamp:before {\n content: \"\\e223\";\n}\n.glyphicon-duplicate:before {\n content: \"\\e224\";\n}\n.glyphicon-piggy-bank:before {\n content: \"\\e225\";\n}\n.glyphicon-scissors:before {\n content: \"\\e226\";\n}\n.glyphicon-bitcoin:before {\n content: \"\\e227\";\n}\n.glyphicon-btc:before {\n content: \"\\e227\";\n}\n.glyphicon-xbt:before {\n content: \"\\e227\";\n}\n.glyphicon-yen:before {\n content: \"\\00a5\";\n}\n.glyphicon-jpy:before {\n content: \"\\00a5\";\n}\n.glyphicon-ruble:before {\n content: \"\\20bd\";\n}\n.glyphicon-rub:before {\n content: \"\\20bd\";\n}\n.glyphicon-scale:before {\n content: \"\\e230\";\n}\n.glyphicon-ice-lolly:before {\n content: \"\\e231\";\n}\n.glyphicon-ice-lolly-tasted:before {\n content: \"\\e232\";\n}\n.glyphicon-education:before {\n content: \"\\e233\";\n}\n.glyphicon-option-horizontal:before {\n content: \"\\e234\";\n}\n.glyphicon-option-vertical:before {\n content: \"\\e235\";\n}\n.glyphicon-menu-hamburger:before {\n content: \"\\e236\";\n}\n.glyphicon-modal-window:before {\n content: \"\\e237\";\n}\n.glyphicon-oil:before {\n content: \"\\e238\";\n}\n.glyphicon-grain:before {\n content: \"\\e239\";\n}\n.glyphicon-sunglasses:before {\n content: \"\\e240\";\n}\n.glyphicon-text-size:before {\n content: \"\\e241\";\n}\n.glyphicon-text-color:before {\n content: \"\\e242\";\n}\n.glyphicon-text-background:before {\n content: \"\\e243\";\n}\n.glyphicon-object-align-top:before {\n content: \"\\e244\";\n}\n.glyphicon-object-align-bottom:before {\n content: \"\\e245\";\n}\n.glyphicon-object-align-horizontal:before {\n content: \"\\e246\";\n}\n.glyphicon-object-align-left:before {\n content: \"\\e247\";\n}\n.glyphicon-object-align-vertical:before {\n content: \"\\e248\";\n}\n.glyphicon-object-align-right:before {\n content: \"\\e249\";\n}\n.glyphicon-triangle-right:before {\n content: \"\\e250\";\n}\n.glyphicon-triangle-left:before {\n content: \"\\e251\";\n}\n.glyphicon-triangle-bottom:before {\n content: \"\\e252\";\n}\n.glyphicon-triangle-top:before {\n content: \"\\e253\";\n}\n.glyphicon-console:before {\n content: \"\\e254\";\n}\n.glyphicon-superscript:before {\n content: \"\\e255\";\n}\n.glyphicon-subscript:before {\n content: \"\\e256\";\n}\n.glyphicon-menu-left:before {\n content: \"\\e257\";\n}\n.glyphicon-menu-right:before {\n content: \"\\e258\";\n}\n.glyphicon-menu-down:before {\n content: \"\\e259\";\n}\n.glyphicon-menu-up:before {\n content: \"\\e260\";\n}\n* {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n*:before,\n*:after {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\nhtml {\n font-size: 10px;\n -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\nbody {\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-size: 14px;\n line-height: 1.42857143;\n color: #333333;\n background-color: #ffffff;\n}\ninput,\nbutton,\nselect,\ntextarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\na {\n color: #337ab7;\n text-decoration: none;\n}\na:hover,\na:focus {\n color: #23527c;\n text-decoration: underline;\n}\na:focus {\n outline: thin dotted;\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\nfigure {\n margin: 0;\n}\nimg {\n vertical-align: middle;\n}\n.img-responsive,\n.thumbnail > img,\n.thumbnail a > img,\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n display: block;\n max-width: 100%;\n height: auto;\n}\n.img-rounded {\n border-radius: 6px;\n}\n.img-thumbnail {\n padding: 4px;\n line-height: 1.42857143;\n background-color: #ffffff;\n border: 1px solid #dddddd;\n border-radius: 4px;\n -webkit-transition: all 0.2s ease-in-out;\n -o-transition: all 0.2s ease-in-out;\n transition: all 0.2s ease-in-out;\n display: inline-block;\n max-width: 100%;\n height: auto;\n}\n.img-circle {\n border-radius: 50%;\n}\nhr {\n margin-top: 20px;\n margin-bottom: 20px;\n border: 0;\n border-top: 1px solid #eeeeee;\n}\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n border: 0;\n}\n.sr-only-focusable:active,\n.sr-only-focusable:focus {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n overflow: visible;\n clip: auto;\n}\n[role=\"button\"] {\n cursor: pointer;\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\n.h1,\n.h2,\n.h3,\n.h4,\n.h5,\n.h6 {\n font-family: inherit;\n font-weight: 500;\n line-height: 1.1;\n color: inherit;\n}\nh1 small,\nh2 small,\nh3 small,\nh4 small,\nh5 small,\nh6 small,\n.h1 small,\n.h2 small,\n.h3 small,\n.h4 small,\n.h5 small,\n.h6 small,\nh1 .small,\nh2 .small,\nh3 .small,\nh4 .small,\nh5 .small,\nh6 .small,\n.h1 .small,\n.h2 .small,\n.h3 .small,\n.h4 .small,\n.h5 .small,\n.h6 .small {\n font-weight: normal;\n line-height: 1;\n color: #777777;\n}\nh1,\n.h1,\nh2,\n.h2,\nh3,\n.h3 {\n margin-top: 20px;\n margin-bottom: 10px;\n}\nh1 small,\n.h1 small,\nh2 small,\n.h2 small,\nh3 small,\n.h3 small,\nh1 .small,\n.h1 .small,\nh2 .small,\n.h2 .small,\nh3 .small,\n.h3 .small {\n font-size: 65%;\n}\nh4,\n.h4,\nh5,\n.h5,\nh6,\n.h6 {\n margin-top: 10px;\n margin-bottom: 10px;\n}\nh4 small,\n.h4 small,\nh5 small,\n.h5 small,\nh6 small,\n.h6 small,\nh4 .small,\n.h4 .small,\nh5 .small,\n.h5 .small,\nh6 .small,\n.h6 .small {\n font-size: 75%;\n}\nh1,\n.h1 {\n font-size: 36px;\n}\nh2,\n.h2 {\n font-size: 30px;\n}\nh3,\n.h3 {\n font-size: 24px;\n}\nh4,\n.h4 {\n font-size: 18px;\n}\nh5,\n.h5 {\n font-size: 14px;\n}\nh6,\n.h6 {\n font-size: 12px;\n}\np {\n margin: 0 0 10px;\n}\n.lead {\n margin-bottom: 20px;\n font-size: 16px;\n font-weight: 300;\n line-height: 1.4;\n}\n@media (min-width: 768px) {\n .lead {\n font-size: 21px;\n }\n}\nsmall,\n.small {\n font-size: 85%;\n}\nmark,\n.mark {\n background-color: #fcf8e3;\n padding: .2em;\n}\n.text-left {\n text-align: left;\n}\n.text-right {\n text-align: right;\n}\n.text-center {\n text-align: center;\n}\n.text-justify {\n text-align: justify;\n}\n.text-nowrap {\n white-space: nowrap;\n}\n.text-lowercase {\n text-transform: lowercase;\n}\n.text-uppercase {\n text-transform: uppercase;\n}\n.text-capitalize {\n text-transform: capitalize;\n}\n.text-muted {\n color: #777777;\n}\n.text-primary {\n color: #337ab7;\n}\na.text-primary:hover {\n color: #286090;\n}\n.text-success {\n color: #3c763d;\n}\na.text-success:hover {\n color: #2b542c;\n}\n.text-info {\n color: #31708f;\n}\na.text-info:hover {\n color: #245269;\n}\n.text-warning {\n color: #8a6d3b;\n}\na.text-warning:hover {\n color: #66512c;\n}\n.text-danger {\n color: #a94442;\n}\na.text-danger:hover {\n color: #843534;\n}\n.bg-primary {\n color: #fff;\n background-color: #337ab7;\n}\na.bg-primary:hover {\n background-color: #286090;\n}\n.bg-success {\n background-color: #dff0d8;\n}\na.bg-success:hover {\n background-color: #c1e2b3;\n}\n.bg-info {\n background-color: #d9edf7;\n}\na.bg-info:hover {\n background-color: #afd9ee;\n}\n.bg-warning {\n background-color: #fcf8e3;\n}\na.bg-warning:hover {\n background-color: #f7ecb5;\n}\n.bg-danger {\n background-color: #f2dede;\n}\na.bg-danger:hover {\n background-color: #e4b9b9;\n}\n.page-header {\n padding-bottom: 9px;\n margin: 40px 0 20px;\n border-bottom: 1px solid #eeeeee;\n}\nul,\nol {\n margin-top: 0;\n margin-bottom: 10px;\n}\nul ul,\nol ul,\nul ol,\nol ol {\n margin-bottom: 0;\n}\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n.list-inline {\n padding-left: 0;\n list-style: none;\n margin-left: -5px;\n}\n.list-inline > li {\n display: inline-block;\n padding-left: 5px;\n padding-right: 5px;\n}\ndl {\n margin-top: 0;\n margin-bottom: 20px;\n}\ndt,\ndd {\n line-height: 1.42857143;\n}\ndt {\n font-weight: bold;\n}\ndd {\n margin-left: 0;\n}\n@media (min-width: 768px) {\n .dl-horizontal dt {\n float: left;\n width: 160px;\n clear: left;\n text-align: right;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .dl-horizontal dd {\n margin-left: 180px;\n }\n}\nabbr[title],\nabbr[data-original-title] {\n cursor: help;\n border-bottom: 1px dotted #777777;\n}\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\nblockquote {\n padding: 10px 20px;\n margin: 0 0 20px;\n font-size: 17.5px;\n border-left: 5px solid #eeeeee;\n}\nblockquote p:last-child,\nblockquote ul:last-child,\nblockquote ol:last-child {\n margin-bottom: 0;\n}\nblockquote footer,\nblockquote small,\nblockquote .small {\n display: block;\n font-size: 80%;\n line-height: 1.42857143;\n color: #777777;\n}\nblockquote footer:before,\nblockquote small:before,\nblockquote .small:before {\n content: '\\2014 \\00A0';\n}\n.blockquote-reverse,\nblockquote.pull-right {\n padding-right: 15px;\n padding-left: 0;\n border-right: 5px solid #eeeeee;\n border-left: 0;\n text-align: right;\n}\n.blockquote-reverse footer:before,\nblockquote.pull-right footer:before,\n.blockquote-reverse small:before,\nblockquote.pull-right small:before,\n.blockquote-reverse .small:before,\nblockquote.pull-right .small:before {\n content: '';\n}\n.blockquote-reverse footer:after,\nblockquote.pull-right footer:after,\n.blockquote-reverse small:after,\nblockquote.pull-right small:after,\n.blockquote-reverse .small:after,\nblockquote.pull-right .small:after {\n content: '\\00A0 \\2014';\n}\naddress {\n margin-bottom: 20px;\n font-style: normal;\n line-height: 1.42857143;\n}\ncode,\nkbd,\npre,\nsamp {\n font-family: Menlo, Monaco, Consolas, \"Courier New\", monospace;\n}\ncode {\n padding: 2px 4px;\n font-size: 90%;\n color: #c7254e;\n background-color: #f9f2f4;\n border-radius: 4px;\n}\nkbd {\n padding: 2px 4px;\n font-size: 90%;\n color: #ffffff;\n background-color: #333333;\n border-radius: 3px;\n box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25);\n}\nkbd kbd {\n padding: 0;\n font-size: 100%;\n font-weight: bold;\n box-shadow: none;\n}\npre {\n display: block;\n padding: 9.5px;\n margin: 0 0 10px;\n font-size: 13px;\n line-height: 1.42857143;\n word-break: break-all;\n word-wrap: break-word;\n color: #333333;\n background-color: #f5f5f5;\n border: 1px solid #cccccc;\n border-radius: 4px;\n}\npre code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre-wrap;\n background-color: transparent;\n border-radius: 0;\n}\n.pre-scrollable {\n max-height: 340px;\n overflow-y: scroll;\n}\n.container {\n margin-right: auto;\n margin-left: auto;\n padding-left: 15px;\n padding-right: 15px;\n}\n@media (min-width: 768px) {\n .container {\n width: 750px;\n }\n}\n@media (min-width: 992px) {\n .container {\n width: 970px;\n }\n}\n@media (min-width: 1200px) {\n .container {\n width: 1170px;\n }\n}\n.container-fluid {\n margin-right: auto;\n margin-left: auto;\n padding-left: 15px;\n padding-right: 15px;\n}\n.row {\n margin-left: -15px;\n margin-right: -15px;\n}\n.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {\n position: relative;\n min-height: 1px;\n padding-left: 15px;\n padding-right: 15px;\n}\n.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 {\n float: left;\n}\n.col-xs-12 {\n width: 100%;\n}\n.col-xs-11 {\n width: 91.66666667%;\n}\n.col-xs-10 {\n width: 83.33333333%;\n}\n.col-xs-9 {\n width: 75%;\n}\n.col-xs-8 {\n width: 66.66666667%;\n}\n.col-xs-7 {\n width: 58.33333333%;\n}\n.col-xs-6 {\n width: 50%;\n}\n.col-xs-5 {\n width: 41.66666667%;\n}\n.col-xs-4 {\n width: 33.33333333%;\n}\n.col-xs-3 {\n width: 25%;\n}\n.col-xs-2 {\n width: 16.66666667%;\n}\n.col-xs-1 {\n width: 8.33333333%;\n}\n.col-xs-pull-12 {\n right: 100%;\n}\n.col-xs-pull-11 {\n right: 91.66666667%;\n}\n.col-xs-pull-10 {\n right: 83.33333333%;\n}\n.col-xs-pull-9 {\n right: 75%;\n}\n.col-xs-pull-8 {\n right: 66.66666667%;\n}\n.col-xs-pull-7 {\n right: 58.33333333%;\n}\n.col-xs-pull-6 {\n right: 50%;\n}\n.col-xs-pull-5 {\n right: 41.66666667%;\n}\n.col-xs-pull-4 {\n right: 33.33333333%;\n}\n.col-xs-pull-3 {\n right: 25%;\n}\n.col-xs-pull-2 {\n right: 16.66666667%;\n}\n.col-xs-pull-1 {\n right: 8.33333333%;\n}\n.col-xs-pull-0 {\n right: auto;\n}\n.col-xs-push-12 {\n left: 100%;\n}\n.col-xs-push-11 {\n left: 91.66666667%;\n}\n.col-xs-push-10 {\n left: 83.33333333%;\n}\n.col-xs-push-9 {\n left: 75%;\n}\n.col-xs-push-8 {\n left: 66.66666667%;\n}\n.col-xs-push-7 {\n left: 58.33333333%;\n}\n.col-xs-push-6 {\n left: 50%;\n}\n.col-xs-push-5 {\n left: 41.66666667%;\n}\n.col-xs-push-4 {\n left: 33.33333333%;\n}\n.col-xs-push-3 {\n left: 25%;\n}\n.col-xs-push-2 {\n left: 16.66666667%;\n}\n.col-xs-push-1 {\n left: 8.33333333%;\n}\n.col-xs-push-0 {\n left: auto;\n}\n.col-xs-offset-12 {\n margin-left: 100%;\n}\n.col-xs-offset-11 {\n margin-left: 91.66666667%;\n}\n.col-xs-offset-10 {\n margin-left: 83.33333333%;\n}\n.col-xs-offset-9 {\n margin-left: 75%;\n}\n.col-xs-offset-8 {\n margin-left: 66.66666667%;\n}\n.col-xs-offset-7 {\n margin-left: 58.33333333%;\n}\n.col-xs-offset-6 {\n margin-left: 50%;\n}\n.col-xs-offset-5 {\n margin-left: 41.66666667%;\n}\n.col-xs-offset-4 {\n margin-left: 33.33333333%;\n}\n.col-xs-offset-3 {\n margin-left: 25%;\n}\n.col-xs-offset-2 {\n margin-left: 16.66666667%;\n}\n.col-xs-offset-1 {\n margin-left: 8.33333333%;\n}\n.col-xs-offset-0 {\n margin-left: 0%;\n}\n@media (min-width: 768px) {\n .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 {\n float: left;\n }\n .col-sm-12 {\n width: 100%;\n }\n .col-sm-11 {\n width: 91.66666667%;\n }\n .col-sm-10 {\n width: 83.33333333%;\n }\n .col-sm-9 {\n width: 75%;\n }\n .col-sm-8 {\n width: 66.66666667%;\n }\n .col-sm-7 {\n width: 58.33333333%;\n }\n .col-sm-6 {\n width: 50%;\n }\n .col-sm-5 {\n width: 41.66666667%;\n }\n .col-sm-4 {\n width: 33.33333333%;\n }\n .col-sm-3 {\n width: 25%;\n }\n .col-sm-2 {\n width: 16.66666667%;\n }\n .col-sm-1 {\n width: 8.33333333%;\n }\n .col-sm-pull-12 {\n right: 100%;\n }\n .col-sm-pull-11 {\n right: 91.66666667%;\n }\n .col-sm-pull-10 {\n right: 83.33333333%;\n }\n .col-sm-pull-9 {\n right: 75%;\n }\n .col-sm-pull-8 {\n right: 66.66666667%;\n }\n .col-sm-pull-7 {\n right: 58.33333333%;\n }\n .col-sm-pull-6 {\n right: 50%;\n }\n .col-sm-pull-5 {\n right: 41.66666667%;\n }\n .col-sm-pull-4 {\n right: 33.33333333%;\n }\n .col-sm-pull-3 {\n right: 25%;\n }\n .col-sm-pull-2 {\n right: 16.66666667%;\n }\n .col-sm-pull-1 {\n right: 8.33333333%;\n }\n .col-sm-pull-0 {\n right: auto;\n }\n .col-sm-push-12 {\n left: 100%;\n }\n .col-sm-push-11 {\n left: 91.66666667%;\n }\n .col-sm-push-10 {\n left: 83.33333333%;\n }\n .col-sm-push-9 {\n left: 75%;\n }\n .col-sm-push-8 {\n left: 66.66666667%;\n }\n .col-sm-push-7 {\n left: 58.33333333%;\n }\n .col-sm-push-6 {\n left: 50%;\n }\n .col-sm-push-5 {\n left: 41.66666667%;\n }\n .col-sm-push-4 {\n left: 33.33333333%;\n }\n .col-sm-push-3 {\n left: 25%;\n }\n .col-sm-push-2 {\n left: 16.66666667%;\n }\n .col-sm-push-1 {\n left: 8.33333333%;\n }\n .col-sm-push-0 {\n left: auto;\n }\n .col-sm-offset-12 {\n margin-left: 100%;\n }\n .col-sm-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-sm-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-sm-offset-9 {\n margin-left: 75%;\n }\n .col-sm-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-sm-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-sm-offset-6 {\n margin-left: 50%;\n }\n .col-sm-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-sm-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-sm-offset-3 {\n margin-left: 25%;\n }\n .col-sm-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-sm-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-sm-offset-0 {\n margin-left: 0%;\n }\n}\n@media (min-width: 992px) {\n .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 {\n float: left;\n }\n .col-md-12 {\n width: 100%;\n }\n .col-md-11 {\n width: 91.66666667%;\n }\n .col-md-10 {\n width: 83.33333333%;\n }\n .col-md-9 {\n width: 75%;\n }\n .col-md-8 {\n width: 66.66666667%;\n }\n .col-md-7 {\n width: 58.33333333%;\n }\n .col-md-6 {\n width: 50%;\n }\n .col-md-5 {\n width: 41.66666667%;\n }\n .col-md-4 {\n width: 33.33333333%;\n }\n .col-md-3 {\n width: 25%;\n }\n .col-md-2 {\n width: 16.66666667%;\n }\n .col-md-1 {\n width: 8.33333333%;\n }\n .col-md-pull-12 {\n right: 100%;\n }\n .col-md-pull-11 {\n right: 91.66666667%;\n }\n .col-md-pull-10 {\n right: 83.33333333%;\n }\n .col-md-pull-9 {\n right: 75%;\n }\n .col-md-pull-8 {\n right: 66.66666667%;\n }\n .col-md-pull-7 {\n right: 58.33333333%;\n }\n .col-md-pull-6 {\n right: 50%;\n }\n .col-md-pull-5 {\n right: 41.66666667%;\n }\n .col-md-pull-4 {\n right: 33.33333333%;\n }\n .col-md-pull-3 {\n right: 25%;\n }\n .col-md-pull-2 {\n right: 16.66666667%;\n }\n .col-md-pull-1 {\n right: 8.33333333%;\n }\n .col-md-pull-0 {\n right: auto;\n }\n .col-md-push-12 {\n left: 100%;\n }\n .col-md-push-11 {\n left: 91.66666667%;\n }\n .col-md-push-10 {\n left: 83.33333333%;\n }\n .col-md-push-9 {\n left: 75%;\n }\n .col-md-push-8 {\n left: 66.66666667%;\n }\n .col-md-push-7 {\n left: 58.33333333%;\n }\n .col-md-push-6 {\n left: 50%;\n }\n .col-md-push-5 {\n left: 41.66666667%;\n }\n .col-md-push-4 {\n left: 33.33333333%;\n }\n .col-md-push-3 {\n left: 25%;\n }\n .col-md-push-2 {\n left: 16.66666667%;\n }\n .col-md-push-1 {\n left: 8.33333333%;\n }\n .col-md-push-0 {\n left: auto;\n }\n .col-md-offset-12 {\n margin-left: 100%;\n }\n .col-md-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-md-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-md-offset-9 {\n margin-left: 75%;\n }\n .col-md-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-md-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-md-offset-6 {\n margin-left: 50%;\n }\n .col-md-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-md-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-md-offset-3 {\n margin-left: 25%;\n }\n .col-md-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-md-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-md-offset-0 {\n margin-left: 0%;\n }\n}\n@media (min-width: 1200px) {\n .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 {\n float: left;\n }\n .col-lg-12 {\n width: 100%;\n }\n .col-lg-11 {\n width: 91.66666667%;\n }\n .col-lg-10 {\n width: 83.33333333%;\n }\n .col-lg-9 {\n width: 75%;\n }\n .col-lg-8 {\n width: 66.66666667%;\n }\n .col-lg-7 {\n width: 58.33333333%;\n }\n .col-lg-6 {\n width: 50%;\n }\n .col-lg-5 {\n width: 41.66666667%;\n }\n .col-lg-4 {\n width: 33.33333333%;\n }\n .col-lg-3 {\n width: 25%;\n }\n .col-lg-2 {\n width: 16.66666667%;\n }\n .col-lg-1 {\n width: 8.33333333%;\n }\n .col-lg-pull-12 {\n right: 100%;\n }\n .col-lg-pull-11 {\n right: 91.66666667%;\n }\n .col-lg-pull-10 {\n right: 83.33333333%;\n }\n .col-lg-pull-9 {\n right: 75%;\n }\n .col-lg-pull-8 {\n right: 66.66666667%;\n }\n .col-lg-pull-7 {\n right: 58.33333333%;\n }\n .col-lg-pull-6 {\n right: 50%;\n }\n .col-lg-pull-5 {\n right: 41.66666667%;\n }\n .col-lg-pull-4 {\n right: 33.33333333%;\n }\n .col-lg-pull-3 {\n right: 25%;\n }\n .col-lg-pull-2 {\n right: 16.66666667%;\n }\n .col-lg-pull-1 {\n right: 8.33333333%;\n }\n .col-lg-pull-0 {\n right: auto;\n }\n .col-lg-push-12 {\n left: 100%;\n }\n .col-lg-push-11 {\n left: 91.66666667%;\n }\n .col-lg-push-10 {\n left: 83.33333333%;\n }\n .col-lg-push-9 {\n left: 75%;\n }\n .col-lg-push-8 {\n left: 66.66666667%;\n }\n .col-lg-push-7 {\n left: 58.33333333%;\n }\n .col-lg-push-6 {\n left: 50%;\n }\n .col-lg-push-5 {\n left: 41.66666667%;\n }\n .col-lg-push-4 {\n left: 33.33333333%;\n }\n .col-lg-push-3 {\n left: 25%;\n }\n .col-lg-push-2 {\n left: 16.66666667%;\n }\n .col-lg-push-1 {\n left: 8.33333333%;\n }\n .col-lg-push-0 {\n left: auto;\n }\n .col-lg-offset-12 {\n margin-left: 100%;\n }\n .col-lg-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-lg-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-lg-offset-9 {\n margin-left: 75%;\n }\n .col-lg-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-lg-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-lg-offset-6 {\n margin-left: 50%;\n }\n .col-lg-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-lg-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-lg-offset-3 {\n margin-left: 25%;\n }\n .col-lg-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-lg-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-lg-offset-0 {\n margin-left: 0%;\n }\n}\ntable {\n background-color: transparent;\n}\ncaption {\n padding-top: 8px;\n padding-bottom: 8px;\n color: #777777;\n text-align: left;\n}\nth {\n text-align: left;\n}\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: 20px;\n}\n.table > thead > tr > th,\n.table > tbody > tr > th,\n.table > tfoot > tr > th,\n.table > thead > tr > td,\n.table > tbody > tr > td,\n.table > tfoot > tr > td {\n padding: 8px;\n line-height: 1.42857143;\n vertical-align: top;\n border-top: 1px solid #dddddd;\n}\n.table > thead > tr > th {\n vertical-align: bottom;\n border-bottom: 2px solid #dddddd;\n}\n.table > caption + thead > tr:first-child > th,\n.table > colgroup + thead > tr:first-child > th,\n.table > thead:first-child > tr:first-child > th,\n.table > caption + thead > tr:first-child > td,\n.table > colgroup + thead > tr:first-child > td,\n.table > thead:first-child > tr:first-child > td {\n border-top: 0;\n}\n.table > tbody + tbody {\n border-top: 2px solid #dddddd;\n}\n.table .table {\n background-color: #ffffff;\n}\n.table-condensed > thead > tr > th,\n.table-condensed > tbody > tr > th,\n.table-condensed > tfoot > tr > th,\n.table-condensed > thead > tr > td,\n.table-condensed > tbody > tr > td,\n.table-condensed > tfoot > tr > td {\n padding: 5px;\n}\n.table-bordered {\n border: 1px solid #dddddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > tbody > tr > th,\n.table-bordered > tfoot > tr > th,\n.table-bordered > thead > tr > td,\n.table-bordered > tbody > tr > td,\n.table-bordered > tfoot > tr > td {\n border: 1px solid #dddddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > thead > tr > td {\n border-bottom-width: 2px;\n}\n.table-striped > tbody > tr:nth-of-type(odd) {\n background-color: #f9f9f9;\n}\n.table-hover > tbody > tr:hover {\n background-color: #f5f5f5;\n}\ntable col[class*=\"col-\"] {\n position: static;\n float: none;\n display: table-column;\n}\ntable td[class*=\"col-\"],\ntable th[class*=\"col-\"] {\n position: static;\n float: none;\n display: table-cell;\n}\n.table > thead > tr > td.active,\n.table > tbody > tr > td.active,\n.table > tfoot > tr > td.active,\n.table > thead > tr > th.active,\n.table > tbody > tr > th.active,\n.table > tfoot > tr > th.active,\n.table > thead > tr.active > td,\n.table > tbody > tr.active > td,\n.table > tfoot > tr.active > td,\n.table > thead > tr.active > th,\n.table > tbody > tr.active > th,\n.table > tfoot > tr.active > th {\n background-color: #f5f5f5;\n}\n.table-hover > tbody > tr > td.active:hover,\n.table-hover > tbody > tr > th.active:hover,\n.table-hover > tbody > tr.active:hover > td,\n.table-hover > tbody > tr:hover > .active,\n.table-hover > tbody > tr.active:hover > th {\n background-color: #e8e8e8;\n}\n.table > thead > tr > td.success,\n.table > tbody > tr > td.success,\n.table > tfoot > tr > td.success,\n.table > thead > tr > th.success,\n.table > tbody > tr > th.success,\n.table > tfoot > tr > th.success,\n.table > thead > tr.success > td,\n.table > tbody > tr.success > td,\n.table > tfoot > tr.success > td,\n.table > thead > tr.success > th,\n.table > tbody > tr.success > th,\n.table > tfoot > tr.success > th {\n background-color: #dff0d8;\n}\n.table-hover > tbody > tr > td.success:hover,\n.table-hover > tbody > tr > th.success:hover,\n.table-hover > tbody > tr.success:hover > td,\n.table-hover > tbody > tr:hover > .success,\n.table-hover > tbody > tr.success:hover > th {\n background-color: #d0e9c6;\n}\n.table > thead > tr > td.info,\n.table > tbody > tr > td.info,\n.table > tfoot > tr > td.info,\n.table > thead > tr > th.info,\n.table > tbody > tr > th.info,\n.table > tfoot > tr > th.info,\n.table > thead > tr.info > td,\n.table > tbody > tr.info > td,\n.table > tfoot > tr.info > td,\n.table > thead > tr.info > th,\n.table > tbody > tr.info > th,\n.table > tfoot > tr.info > th {\n background-color: #d9edf7;\n}\n.table-hover > tbody > tr > td.info:hover,\n.table-hover > tbody > tr > th.info:hover,\n.table-hover > tbody > tr.info:hover > td,\n.table-hover > tbody > tr:hover > .info,\n.table-hover > tbody > tr.info:hover > th {\n background-color: #c4e3f3;\n}\n.table > thead > tr > td.warning,\n.table > tbody > tr > td.warning,\n.table > tfoot > tr > td.warning,\n.table > thead > tr > th.warning,\n.table > tbody > tr > th.warning,\n.table > tfoot > tr > th.warning,\n.table > thead > tr.warning > td,\n.table > tbody > tr.warning > td,\n.table > tfoot > tr.warning > td,\n.table > thead > tr.warning > th,\n.table > tbody > tr.warning > th,\n.table > tfoot > tr.warning > th {\n background-color: #fcf8e3;\n}\n.table-hover > tbody > tr > td.warning:hover,\n.table-hover > tbody > tr > th.warning:hover,\n.table-hover > tbody > tr.warning:hover > td,\n.table-hover > tbody > tr:hover > .warning,\n.table-hover > tbody > tr.warning:hover > th {\n background-color: #faf2cc;\n}\n.table > thead > tr > td.danger,\n.table > tbody > tr > td.danger,\n.table > tfoot > tr > td.danger,\n.table > thead > tr > th.danger,\n.table > tbody > tr > th.danger,\n.table > tfoot > tr > th.danger,\n.table > thead > tr.danger > td,\n.table > tbody > tr.danger > td,\n.table > tfoot > tr.danger > td,\n.table > thead > tr.danger > th,\n.table > tbody > tr.danger > th,\n.table > tfoot > tr.danger > th {\n background-color: #f2dede;\n}\n.table-hover > tbody > tr > td.danger:hover,\n.table-hover > tbody > tr > th.danger:hover,\n.table-hover > tbody > tr.danger:hover > td,\n.table-hover > tbody > tr:hover > .danger,\n.table-hover > tbody > tr.danger:hover > th {\n background-color: #ebcccc;\n}\n.table-responsive {\n overflow-x: auto;\n min-height: 0.01%;\n}\n@media screen and (max-width: 767px) {\n .table-responsive {\n width: 100%;\n margin-bottom: 15px;\n overflow-y: hidden;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n border: 1px solid #dddddd;\n }\n .table-responsive > .table {\n margin-bottom: 0;\n }\n .table-responsive > .table > thead > tr > th,\n .table-responsive > .table > tbody > tr > th,\n .table-responsive > .table > tfoot > tr > th,\n .table-responsive > .table > thead > tr > td,\n .table-responsive > .table > tbody > tr > td,\n .table-responsive > .table > tfoot > tr > td {\n white-space: nowrap;\n }\n .table-responsive > .table-bordered {\n border: 0;\n }\n .table-responsive > .table-bordered > thead > tr > th:first-child,\n .table-responsive > .table-bordered > tbody > tr > th:first-child,\n .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n .table-responsive > .table-bordered > thead > tr > td:first-child,\n .table-responsive > .table-bordered > tbody > tr > td:first-child,\n .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n border-left: 0;\n }\n .table-responsive > .table-bordered > thead > tr > th:last-child,\n .table-responsive > .table-bordered > tbody > tr > th:last-child,\n .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n .table-responsive > .table-bordered > thead > tr > td:last-child,\n .table-responsive > .table-bordered > tbody > tr > td:last-child,\n .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n border-right: 0;\n }\n .table-responsive > .table-bordered > tbody > tr:last-child > th,\n .table-responsive > .table-bordered > tfoot > tr:last-child > th,\n .table-responsive > .table-bordered > tbody > tr:last-child > td,\n .table-responsive > .table-bordered > tfoot > tr:last-child > td {\n border-bottom: 0;\n }\n}\nfieldset {\n padding: 0;\n margin: 0;\n border: 0;\n min-width: 0;\n}\nlegend {\n display: block;\n width: 100%;\n padding: 0;\n margin-bottom: 20px;\n font-size: 21px;\n line-height: inherit;\n color: #333333;\n border: 0;\n border-bottom: 1px solid #e5e5e5;\n}\nlabel {\n display: inline-block;\n max-width: 100%;\n margin-bottom: 5px;\n font-weight: bold;\n}\ninput[type=\"search\"] {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n margin: 4px 0 0;\n margin-top: 1px \\9;\n line-height: normal;\n}\ninput[type=\"file\"] {\n display: block;\n}\ninput[type=\"range\"] {\n display: block;\n width: 100%;\n}\nselect[multiple],\nselect[size] {\n height: auto;\n}\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n outline: thin dotted;\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\noutput {\n display: block;\n padding-top: 7px;\n font-size: 14px;\n line-height: 1.42857143;\n color: #555555;\n}\n.form-control {\n display: block;\n width: 100%;\n height: 34px;\n padding: 6px 12px;\n font-size: 14px;\n line-height: 1.42857143;\n color: #555555;\n background-color: #ffffff;\n background-image: none;\n border: 1px solid #cccccc;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n}\n.form-control:focus {\n border-color: #66afe9;\n outline: 0;\n -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);\n box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);\n}\n.form-control::-moz-placeholder {\n color: #999999;\n opacity: 1;\n}\n.form-control:-ms-input-placeholder {\n color: #999999;\n}\n.form-control::-webkit-input-placeholder {\n color: #999999;\n}\n.form-control[disabled],\n.form-control[readonly],\nfieldset[disabled] .form-control {\n background-color: #eeeeee;\n opacity: 1;\n}\n.form-control[disabled],\nfieldset[disabled] .form-control {\n cursor: not-allowed;\n}\ntextarea.form-control {\n height: auto;\n}\ninput[type=\"search\"] {\n -webkit-appearance: none;\n}\n@media screen and (-webkit-min-device-pixel-ratio: 0) {\n input[type=\"date\"],\n input[type=\"time\"],\n input[type=\"datetime-local\"],\n input[type=\"month\"] {\n line-height: 34px;\n }\n input[type=\"date\"].input-sm,\n input[type=\"time\"].input-sm,\n input[type=\"datetime-local\"].input-sm,\n input[type=\"month\"].input-sm,\n .input-group-sm input[type=\"date\"],\n .input-group-sm input[type=\"time\"],\n .input-group-sm input[type=\"datetime-local\"],\n .input-group-sm input[type=\"month\"] {\n line-height: 30px;\n }\n input[type=\"date\"].input-lg,\n input[type=\"time\"].input-lg,\n input[type=\"datetime-local\"].input-lg,\n input[type=\"month\"].input-lg,\n .input-group-lg input[type=\"date\"],\n .input-group-lg input[type=\"time\"],\n .input-group-lg input[type=\"datetime-local\"],\n .input-group-lg input[type=\"month\"] {\n line-height: 46px;\n }\n}\n.form-group {\n margin-bottom: 15px;\n}\n.radio,\n.checkbox {\n position: relative;\n display: block;\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.radio label,\n.checkbox label {\n min-height: 20px;\n padding-left: 20px;\n margin-bottom: 0;\n font-weight: normal;\n cursor: pointer;\n}\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n position: absolute;\n margin-left: -20px;\n margin-top: 4px \\9;\n}\n.radio + .radio,\n.checkbox + .checkbox {\n margin-top: -5px;\n}\n.radio-inline,\n.checkbox-inline {\n position: relative;\n display: inline-block;\n padding-left: 20px;\n margin-bottom: 0;\n vertical-align: middle;\n font-weight: normal;\n cursor: pointer;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n margin-top: 0;\n margin-left: 10px;\n}\ninput[type=\"radio\"][disabled],\ninput[type=\"checkbox\"][disabled],\ninput[type=\"radio\"].disabled,\ninput[type=\"checkbox\"].disabled,\nfieldset[disabled] input[type=\"radio\"],\nfieldset[disabled] input[type=\"checkbox\"] {\n cursor: not-allowed;\n}\n.radio-inline.disabled,\n.checkbox-inline.disabled,\nfieldset[disabled] .radio-inline,\nfieldset[disabled] .checkbox-inline {\n cursor: not-allowed;\n}\n.radio.disabled label,\n.checkbox.disabled label,\nfieldset[disabled] .radio label,\nfieldset[disabled] .checkbox label {\n cursor: not-allowed;\n}\n.form-control-static {\n padding-top: 7px;\n padding-bottom: 7px;\n margin-bottom: 0;\n min-height: 34px;\n}\n.form-control-static.input-lg,\n.form-control-static.input-sm {\n padding-left: 0;\n padding-right: 0;\n}\n.input-sm {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.input-sm {\n height: 30px;\n line-height: 30px;\n}\ntextarea.input-sm,\nselect[multiple].input-sm {\n height: auto;\n}\n.form-group-sm .form-control {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.form-group-sm .form-control {\n height: 30px;\n line-height: 30px;\n}\ntextarea.form-group-sm .form-control,\nselect[multiple].form-group-sm .form-control {\n height: auto;\n}\n.form-group-sm .form-control-static {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n min-height: 32px;\n}\n.input-lg {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.input-lg {\n height: 46px;\n line-height: 46px;\n}\ntextarea.input-lg,\nselect[multiple].input-lg {\n height: auto;\n}\n.form-group-lg .form-control {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.form-group-lg .form-control {\n height: 46px;\n line-height: 46px;\n}\ntextarea.form-group-lg .form-control,\nselect[multiple].form-group-lg .form-control {\n height: auto;\n}\n.form-group-lg .form-control-static {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n min-height: 38px;\n}\n.has-feedback {\n position: relative;\n}\n.has-feedback .form-control {\n padding-right: 42.5px;\n}\n.form-control-feedback {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2;\n display: block;\n width: 34px;\n height: 34px;\n line-height: 34px;\n text-align: center;\n pointer-events: none;\n}\n.input-lg + .form-control-feedback {\n width: 46px;\n height: 46px;\n line-height: 46px;\n}\n.input-sm + .form-control-feedback {\n width: 30px;\n height: 30px;\n line-height: 30px;\n}\n.has-success .help-block,\n.has-success .control-label,\n.has-success .radio,\n.has-success .checkbox,\n.has-success .radio-inline,\n.has-success .checkbox-inline,\n.has-success.radio label,\n.has-success.checkbox label,\n.has-success.radio-inline label,\n.has-success.checkbox-inline label {\n color: #3c763d;\n}\n.has-success .form-control {\n border-color: #3c763d;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-success .form-control:focus {\n border-color: #2b542c;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;\n}\n.has-success .input-group-addon {\n color: #3c763d;\n border-color: #3c763d;\n background-color: #dff0d8;\n}\n.has-success .form-control-feedback {\n color: #3c763d;\n}\n.has-warning .help-block,\n.has-warning .control-label,\n.has-warning .radio,\n.has-warning .checkbox,\n.has-warning .radio-inline,\n.has-warning .checkbox-inline,\n.has-warning.radio label,\n.has-warning.checkbox label,\n.has-warning.radio-inline label,\n.has-warning.checkbox-inline label {\n color: #8a6d3b;\n}\n.has-warning .form-control {\n border-color: #8a6d3b;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-warning .form-control:focus {\n border-color: #66512c;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;\n}\n.has-warning .input-group-addon {\n color: #8a6d3b;\n border-color: #8a6d3b;\n background-color: #fcf8e3;\n}\n.has-warning .form-control-feedback {\n color: #8a6d3b;\n}\n.has-error .help-block,\n.has-error .control-label,\n.has-error .radio,\n.has-error .checkbox,\n.has-error .radio-inline,\n.has-error .checkbox-inline,\n.has-error.radio label,\n.has-error.checkbox label,\n.has-error.radio-inline label,\n.has-error.checkbox-inline label {\n color: #a94442;\n}\n.has-error .form-control {\n border-color: #a94442;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-error .form-control:focus {\n border-color: #843534;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;\n}\n.has-error .input-group-addon {\n color: #a94442;\n border-color: #a94442;\n background-color: #f2dede;\n}\n.has-error .form-control-feedback {\n color: #a94442;\n}\n.has-feedback label ~ .form-control-feedback {\n top: 25px;\n}\n.has-feedback label.sr-only ~ .form-control-feedback {\n top: 0;\n}\n.help-block {\n display: block;\n margin-top: 5px;\n margin-bottom: 10px;\n color: #737373;\n}\n@media (min-width: 768px) {\n .form-inline .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .form-inline .form-control-static {\n display: inline-block;\n }\n .form-inline .input-group {\n display: inline-table;\n vertical-align: middle;\n }\n .form-inline .input-group .input-group-addon,\n .form-inline .input-group .input-group-btn,\n .form-inline .input-group .form-control {\n width: auto;\n }\n .form-inline .input-group > .form-control {\n width: 100%;\n }\n .form-inline .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .radio,\n .form-inline .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .radio label,\n .form-inline .checkbox label {\n padding-left: 0;\n }\n .form-inline .radio input[type=\"radio\"],\n .form-inline .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n .form-inline .has-feedback .form-control-feedback {\n top: 0;\n }\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox,\n.form-horizontal .radio-inline,\n.form-horizontal .checkbox-inline {\n margin-top: 0;\n margin-bottom: 0;\n padding-top: 7px;\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox {\n min-height: 27px;\n}\n.form-horizontal .form-group {\n margin-left: -15px;\n margin-right: -15px;\n}\n@media (min-width: 768px) {\n .form-horizontal .control-label {\n text-align: right;\n margin-bottom: 0;\n padding-top: 7px;\n }\n}\n.form-horizontal .has-feedback .form-control-feedback {\n right: 15px;\n}\n@media (min-width: 768px) {\n .form-horizontal .form-group-lg .control-label {\n padding-top: 14.333333px;\n }\n}\n@media (min-width: 768px) {\n .form-horizontal .form-group-sm .control-label {\n padding-top: 6px;\n }\n}\n.btn {\n display: inline-block;\n margin-bottom: 0;\n font-weight: normal;\n text-align: center;\n vertical-align: middle;\n touch-action: manipulation;\n cursor: pointer;\n background-image: none;\n border: 1px solid transparent;\n white-space: nowrap;\n padding: 6px 12px;\n font-size: 14px;\n line-height: 1.42857143;\n border-radius: 4px;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n.btn:focus,\n.btn:active:focus,\n.btn.active:focus,\n.btn.focus,\n.btn:active.focus,\n.btn.active.focus {\n outline: thin dotted;\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n.btn:hover,\n.btn:focus,\n.btn.focus {\n color: #333333;\n text-decoration: none;\n}\n.btn:active,\n.btn.active {\n outline: 0;\n background-image: none;\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn.disabled,\n.btn[disabled],\nfieldset[disabled] .btn {\n cursor: not-allowed;\n pointer-events: none;\n opacity: 0.65;\n filter: alpha(opacity=65);\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn-default {\n color: #333333;\n background-color: #ffffff;\n border-color: #cccccc;\n}\n.btn-default:hover,\n.btn-default:focus,\n.btn-default.focus,\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n color: #333333;\n background-color: #e6e6e6;\n border-color: #adadad;\n}\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n background-image: none;\n}\n.btn-default.disabled,\n.btn-default[disabled],\nfieldset[disabled] .btn-default,\n.btn-default.disabled:hover,\n.btn-default[disabled]:hover,\nfieldset[disabled] .btn-default:hover,\n.btn-default.disabled:focus,\n.btn-default[disabled]:focus,\nfieldset[disabled] .btn-default:focus,\n.btn-default.disabled.focus,\n.btn-default[disabled].focus,\nfieldset[disabled] .btn-default.focus,\n.btn-default.disabled:active,\n.btn-default[disabled]:active,\nfieldset[disabled] .btn-default:active,\n.btn-default.disabled.active,\n.btn-default[disabled].active,\nfieldset[disabled] .btn-default.active {\n background-color: #ffffff;\n border-color: #cccccc;\n}\n.btn-default .badge {\n color: #ffffff;\n background-color: #333333;\n}\n.btn-primary {\n color: #ffffff;\n background-color: #337ab7;\n border-color: #2e6da4;\n}\n.btn-primary:hover,\n.btn-primary:focus,\n.btn-primary.focus,\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n color: #ffffff;\n background-color: #286090;\n border-color: #204d74;\n}\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n background-image: none;\n}\n.btn-primary.disabled,\n.btn-primary[disabled],\nfieldset[disabled] .btn-primary,\n.btn-primary.disabled:hover,\n.btn-primary[disabled]:hover,\nfieldset[disabled] .btn-primary:hover,\n.btn-primary.disabled:focus,\n.btn-primary[disabled]:focus,\nfieldset[disabled] .btn-primary:focus,\n.btn-primary.disabled.focus,\n.btn-primary[disabled].focus,\nfieldset[disabled] .btn-primary.focus,\n.btn-primary.disabled:active,\n.btn-primary[disabled]:active,\nfieldset[disabled] .btn-primary:active,\n.btn-primary.disabled.active,\n.btn-primary[disabled].active,\nfieldset[disabled] .btn-primary.active {\n background-color: #337ab7;\n border-color: #2e6da4;\n}\n.btn-primary .badge {\n color: #337ab7;\n background-color: #ffffff;\n}\n.btn-success {\n color: #ffffff;\n background-color: #5cb85c;\n border-color: #4cae4c;\n}\n.btn-success:hover,\n.btn-success:focus,\n.btn-success.focus,\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n color: #ffffff;\n background-color: #449d44;\n border-color: #398439;\n}\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n background-image: none;\n}\n.btn-success.disabled,\n.btn-success[disabled],\nfieldset[disabled] .btn-success,\n.btn-success.disabled:hover,\n.btn-success[disabled]:hover,\nfieldset[disabled] .btn-success:hover,\n.btn-success.disabled:focus,\n.btn-success[disabled]:focus,\nfieldset[disabled] .btn-success:focus,\n.btn-success.disabled.focus,\n.btn-success[disabled].focus,\nfieldset[disabled] .btn-success.focus,\n.btn-success.disabled:active,\n.btn-success[disabled]:active,\nfieldset[disabled] .btn-success:active,\n.btn-success.disabled.active,\n.btn-success[disabled].active,\nfieldset[disabled] .btn-success.active {\n background-color: #5cb85c;\n border-color: #4cae4c;\n}\n.btn-success .badge {\n color: #5cb85c;\n background-color: #ffffff;\n}\n.btn-info {\n color: #ffffff;\n background-color: #5bc0de;\n border-color: #46b8da;\n}\n.btn-info:hover,\n.btn-info:focus,\n.btn-info.focus,\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n color: #ffffff;\n background-color: #31b0d5;\n border-color: #269abc;\n}\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n background-image: none;\n}\n.btn-info.disabled,\n.btn-info[disabled],\nfieldset[disabled] .btn-info,\n.btn-info.disabled:hover,\n.btn-info[disabled]:hover,\nfieldset[disabled] .btn-info:hover,\n.btn-info.disabled:focus,\n.btn-info[disabled]:focus,\nfieldset[disabled] .btn-info:focus,\n.btn-info.disabled.focus,\n.btn-info[disabled].focus,\nfieldset[disabled] .btn-info.focus,\n.btn-info.disabled:active,\n.btn-info[disabled]:active,\nfieldset[disabled] .btn-info:active,\n.btn-info.disabled.active,\n.btn-info[disabled].active,\nfieldset[disabled] .btn-info.active {\n background-color: #5bc0de;\n border-color: #46b8da;\n}\n.btn-info .badge {\n color: #5bc0de;\n background-color: #ffffff;\n}\n.btn-warning {\n color: #ffffff;\n background-color: #f0ad4e;\n border-color: #eea236;\n}\n.btn-warning:hover,\n.btn-warning:focus,\n.btn-warning.focus,\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n color: #ffffff;\n background-color: #ec971f;\n border-color: #d58512;\n}\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n background-image: none;\n}\n.btn-warning.disabled,\n.btn-warning[disabled],\nfieldset[disabled] .btn-warning,\n.btn-warning.disabled:hover,\n.btn-warning[disabled]:hover,\nfieldset[disabled] .btn-warning:hover,\n.btn-warning.disabled:focus,\n.btn-warning[disabled]:focus,\nfieldset[disabled] .btn-warning:focus,\n.btn-warning.disabled.focus,\n.btn-warning[disabled].focus,\nfieldset[disabled] .btn-warning.focus,\n.btn-warning.disabled:active,\n.btn-warning[disabled]:active,\nfieldset[disabled] .btn-warning:active,\n.btn-warning.disabled.active,\n.btn-warning[disabled].active,\nfieldset[disabled] .btn-warning.active {\n background-color: #f0ad4e;\n border-color: #eea236;\n}\n.btn-warning .badge {\n color: #f0ad4e;\n background-color: #ffffff;\n}\n.btn-danger {\n color: #ffffff;\n background-color: #d9534f;\n border-color: #d43f3a;\n}\n.btn-danger:hover,\n.btn-danger:focus,\n.btn-danger.focus,\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n color: #ffffff;\n background-color: #c9302c;\n border-color: #ac2925;\n}\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n background-image: none;\n}\n.btn-danger.disabled,\n.btn-danger[disabled],\nfieldset[disabled] .btn-danger,\n.btn-danger.disabled:hover,\n.btn-danger[disabled]:hover,\nfieldset[disabled] .btn-danger:hover,\n.btn-danger.disabled:focus,\n.btn-danger[disabled]:focus,\nfieldset[disabled] .btn-danger:focus,\n.btn-danger.disabled.focus,\n.btn-danger[disabled].focus,\nfieldset[disabled] .btn-danger.focus,\n.btn-danger.disabled:active,\n.btn-danger[disabled]:active,\nfieldset[disabled] .btn-danger:active,\n.btn-danger.disabled.active,\n.btn-danger[disabled].active,\nfieldset[disabled] .btn-danger.active {\n background-color: #d9534f;\n border-color: #d43f3a;\n}\n.btn-danger .badge {\n color: #d9534f;\n background-color: #ffffff;\n}\n.btn-link {\n color: #337ab7;\n font-weight: normal;\n border-radius: 0;\n}\n.btn-link,\n.btn-link:active,\n.btn-link.active,\n.btn-link[disabled],\nfieldset[disabled] .btn-link {\n background-color: transparent;\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn-link,\n.btn-link:hover,\n.btn-link:focus,\n.btn-link:active {\n border-color: transparent;\n}\n.btn-link:hover,\n.btn-link:focus {\n color: #23527c;\n text-decoration: underline;\n background-color: transparent;\n}\n.btn-link[disabled]:hover,\nfieldset[disabled] .btn-link:hover,\n.btn-link[disabled]:focus,\nfieldset[disabled] .btn-link:focus {\n color: #777777;\n text-decoration: none;\n}\n.btn-lg,\n.btn-group-lg > .btn {\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\n.btn-sm,\n.btn-group-sm > .btn {\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.btn-xs,\n.btn-group-xs > .btn {\n padding: 1px 5px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.btn-block {\n display: block;\n width: 100%;\n}\n.btn-block + .btn-block {\n margin-top: 5px;\n}\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n width: 100%;\n}\n.fade {\n opacity: 0;\n -webkit-transition: opacity 0.15s linear;\n -o-transition: opacity 0.15s linear;\n transition: opacity 0.15s linear;\n}\n.fade.in {\n opacity: 1;\n}\n.collapse {\n display: none;\n}\n.collapse.in {\n display: block;\n}\ntr.collapse.in {\n display: table-row;\n}\ntbody.collapse.in {\n display: table-row-group;\n}\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n -webkit-transition-property: height, visibility;\n transition-property: height, visibility;\n -webkit-transition-duration: 0.35s;\n transition-duration: 0.35s;\n -webkit-transition-timing-function: ease;\n transition-timing-function: ease;\n}\n.caret {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 2px;\n vertical-align: middle;\n border-top: 4px dashed;\n border-right: 4px solid transparent;\n border-left: 4px solid transparent;\n}\n.dropup,\n.dropdown {\n position: relative;\n}\n.dropdown-toggle:focus {\n outline: 0;\n}\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: 1000;\n display: none;\n float: left;\n min-width: 160px;\n padding: 5px 0;\n margin: 2px 0 0;\n list-style: none;\n font-size: 14px;\n text-align: left;\n background-color: #ffffff;\n border: 1px solid #cccccc;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 4px;\n -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\n box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\n background-clip: padding-box;\n}\n.dropdown-menu.pull-right {\n right: 0;\n left: auto;\n}\n.dropdown-menu .divider {\n height: 1px;\n margin: 9px 0;\n overflow: hidden;\n background-color: #e5e5e5;\n}\n.dropdown-menu > li > a {\n display: block;\n padding: 3px 20px;\n clear: both;\n font-weight: normal;\n line-height: 1.42857143;\n color: #333333;\n white-space: nowrap;\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n text-decoration: none;\n color: #262626;\n background-color: #f5f5f5;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n color: #ffffff;\n text-decoration: none;\n outline: 0;\n background-color: #337ab7;\n}\n.dropdown-menu > .disabled > a,\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n color: #777777;\n}\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n text-decoration: none;\n background-color: transparent;\n background-image: none;\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n cursor: not-allowed;\n}\n.open > .dropdown-menu {\n display: block;\n}\n.open > a {\n outline: 0;\n}\n.dropdown-menu-right {\n left: auto;\n right: 0;\n}\n.dropdown-menu-left {\n left: 0;\n right: auto;\n}\n.dropdown-header {\n display: block;\n padding: 3px 20px;\n font-size: 12px;\n line-height: 1.42857143;\n color: #777777;\n white-space: nowrap;\n}\n.dropdown-backdrop {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n top: 0;\n z-index: 990;\n}\n.pull-right > .dropdown-menu {\n right: 0;\n left: auto;\n}\n.dropup .caret,\n.navbar-fixed-bottom .dropdown .caret {\n border-top: 0;\n border-bottom: 4px solid;\n content: \"\";\n}\n.dropup .dropdown-menu,\n.navbar-fixed-bottom .dropdown .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-bottom: 2px;\n}\n@media (min-width: 768px) {\n .navbar-right .dropdown-menu {\n left: auto;\n right: 0;\n }\n .navbar-right .dropdown-menu-left {\n left: 0;\n right: auto;\n }\n}\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-block;\n vertical-align: middle;\n}\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n position: relative;\n float: left;\n}\n.btn-group > .btn:hover,\n.btn-group-vertical > .btn:hover,\n.btn-group > .btn:focus,\n.btn-group-vertical > .btn:focus,\n.btn-group > .btn:active,\n.btn-group-vertical > .btn:active,\n.btn-group > .btn.active,\n.btn-group-vertical > .btn.active {\n z-index: 2;\n}\n.btn-group .btn + .btn,\n.btn-group .btn + .btn-group,\n.btn-group .btn-group + .btn,\n.btn-group .btn-group + .btn-group {\n margin-left: -1px;\n}\n.btn-toolbar {\n margin-left: -5px;\n}\n.btn-toolbar .btn-group,\n.btn-toolbar .input-group {\n float: left;\n}\n.btn-toolbar > .btn,\n.btn-toolbar > .btn-group,\n.btn-toolbar > .input-group {\n margin-left: 5px;\n}\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n border-radius: 0;\n}\n.btn-group > .btn:first-child {\n margin-left: 0;\n}\n.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group > .btn-group {\n float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n outline: 0;\n}\n.btn-group > .btn + .dropdown-toggle {\n padding-left: 8px;\n padding-right: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n padding-left: 12px;\n padding-right: 12px;\n}\n.btn-group.open .dropdown-toggle {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-group.open .dropdown-toggle.btn-link {\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn .caret {\n margin-left: 0;\n}\n.btn-lg .caret {\n border-width: 5px 5px 0;\n border-bottom-width: 0;\n}\n.dropup .btn-lg .caret {\n border-width: 0 5px 5px;\n}\n.btn-group-vertical > .btn,\n.btn-group-vertical > .btn-group,\n.btn-group-vertical > .btn-group > .btn {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n}\n.btn-group-vertical > .btn-group > .btn {\n float: none;\n}\n.btn-group-vertical > .btn + .btn,\n.btn-group-vertical > .btn + .btn-group,\n.btn-group-vertical > .btn-group + .btn,\n.btn-group-vertical > .btn-group + .btn-group {\n margin-top: -1px;\n margin-left: 0;\n}\n.btn-group-vertical > .btn:not(:first-child):not(:last-child) {\n border-radius: 0;\n}\n.btn-group-vertical > .btn:first-child:not(:last-child) {\n border-top-right-radius: 4px;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn:last-child:not(:first-child) {\n border-bottom-left-radius: 4px;\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group-justified {\n display: table;\n width: 100%;\n table-layout: fixed;\n border-collapse: separate;\n}\n.btn-group-justified > .btn,\n.btn-group-justified > .btn-group {\n float: none;\n display: table-cell;\n width: 1%;\n}\n.btn-group-justified > .btn-group .btn {\n width: 100%;\n}\n.btn-group-justified > .btn-group .dropdown-menu {\n left: auto;\n}\n[data-toggle=\"buttons\"] > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn input[type=\"checkbox\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0, 0, 0, 0);\n pointer-events: none;\n}\n.input-group {\n position: relative;\n display: table;\n border-collapse: separate;\n}\n.input-group[class*=\"col-\"] {\n float: none;\n padding-left: 0;\n padding-right: 0;\n}\n.input-group .form-control {\n position: relative;\n z-index: 2;\n float: left;\n width: 100%;\n margin-bottom: 0;\n}\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.input-group-lg > .form-control,\nselect.input-group-lg > .input-group-addon,\nselect.input-group-lg > .input-group-btn > .btn {\n height: 46px;\n line-height: 46px;\n}\ntextarea.input-group-lg > .form-control,\ntextarea.input-group-lg > .input-group-addon,\ntextarea.input-group-lg > .input-group-btn > .btn,\nselect[multiple].input-group-lg > .form-control,\nselect[multiple].input-group-lg > .input-group-addon,\nselect[multiple].input-group-lg > .input-group-btn > .btn {\n height: auto;\n}\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.input-group-sm > .form-control,\nselect.input-group-sm > .input-group-addon,\nselect.input-group-sm > .input-group-btn > .btn {\n height: 30px;\n line-height: 30px;\n}\ntextarea.input-group-sm > .form-control,\ntextarea.input-group-sm > .input-group-addon,\ntextarea.input-group-sm > .input-group-btn > .btn,\nselect[multiple].input-group-sm > .form-control,\nselect[multiple].input-group-sm > .input-group-addon,\nselect[multiple].input-group-sm > .input-group-btn > .btn {\n height: auto;\n}\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n display: table-cell;\n}\n.input-group-addon:not(:first-child):not(:last-child),\n.input-group-btn:not(:first-child):not(:last-child),\n.input-group .form-control:not(:first-child):not(:last-child) {\n border-radius: 0;\n}\n.input-group-addon,\n.input-group-btn {\n width: 1%;\n white-space: nowrap;\n vertical-align: middle;\n}\n.input-group-addon {\n padding: 6px 12px;\n font-size: 14px;\n font-weight: normal;\n line-height: 1;\n color: #555555;\n text-align: center;\n background-color: #eeeeee;\n border: 1px solid #cccccc;\n border-radius: 4px;\n}\n.input-group-addon.input-sm {\n padding: 5px 10px;\n font-size: 12px;\n border-radius: 3px;\n}\n.input-group-addon.input-lg {\n padding: 10px 16px;\n font-size: 18px;\n border-radius: 6px;\n}\n.input-group-addon input[type=\"radio\"],\n.input-group-addon input[type=\"checkbox\"] {\n margin-top: 0;\n}\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.input-group-addon:first-child {\n border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n}\n.input-group-addon:last-child {\n border-left: 0;\n}\n.input-group-btn {\n position: relative;\n font-size: 0;\n white-space: nowrap;\n}\n.input-group-btn > .btn {\n position: relative;\n}\n.input-group-btn > .btn + .btn {\n margin-left: -1px;\n}\n.input-group-btn > .btn:hover,\n.input-group-btn > .btn:focus,\n.input-group-btn > .btn:active {\n z-index: 2;\n}\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group {\n margin-right: -1px;\n}\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group {\n margin-left: -1px;\n}\n.nav {\n margin-bottom: 0;\n padding-left: 0;\n list-style: none;\n}\n.nav > li {\n position: relative;\n display: block;\n}\n.nav > li > a {\n position: relative;\n display: block;\n padding: 10px 15px;\n}\n.nav > li > a:hover,\n.nav > li > a:focus {\n text-decoration: none;\n background-color: #eeeeee;\n}\n.nav > li.disabled > a {\n color: #777777;\n}\n.nav > li.disabled > a:hover,\n.nav > li.disabled > a:focus {\n color: #777777;\n text-decoration: none;\n background-color: transparent;\n cursor: not-allowed;\n}\n.nav .open > a,\n.nav .open > a:hover,\n.nav .open > a:focus {\n background-color: #eeeeee;\n border-color: #337ab7;\n}\n.nav .nav-divider {\n height: 1px;\n margin: 9px 0;\n overflow: hidden;\n background-color: #e5e5e5;\n}\n.nav > li > a > img {\n max-width: none;\n}\n.nav-tabs {\n border-bottom: 1px solid #dddddd;\n}\n.nav-tabs > li {\n float: left;\n margin-bottom: -1px;\n}\n.nav-tabs > li > a {\n margin-right: 2px;\n line-height: 1.42857143;\n border: 1px solid transparent;\n border-radius: 4px 4px 0 0;\n}\n.nav-tabs > li > a:hover {\n border-color: #eeeeee #eeeeee #dddddd;\n}\n.nav-tabs > li.active > a,\n.nav-tabs > li.active > a:hover,\n.nav-tabs > li.active > a:focus {\n color: #555555;\n background-color: #ffffff;\n border: 1px solid #dddddd;\n border-bottom-color: transparent;\n cursor: default;\n}\n.nav-tabs.nav-justified {\n width: 100%;\n border-bottom: 0;\n}\n.nav-tabs.nav-justified > li {\n float: none;\n}\n.nav-tabs.nav-justified > li > a {\n text-align: center;\n margin-bottom: 5px;\n}\n.nav-tabs.nav-justified > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n}\n@media (min-width: 768px) {\n .nav-tabs.nav-justified > li {\n display: table-cell;\n width: 1%;\n }\n .nav-tabs.nav-justified > li > a {\n margin-bottom: 0;\n }\n}\n.nav-tabs.nav-justified > li > a {\n margin-right: 0;\n border-radius: 4px;\n}\n.nav-tabs.nav-justified > .active > a,\n.nav-tabs.nav-justified > .active > a:hover,\n.nav-tabs.nav-justified > .active > a:focus {\n border: 1px solid #dddddd;\n}\n@media (min-width: 768px) {\n .nav-tabs.nav-justified > li > a {\n border-bottom: 1px solid #dddddd;\n border-radius: 4px 4px 0 0;\n }\n .nav-tabs.nav-justified > .active > a,\n .nav-tabs.nav-justified > .active > a:hover,\n .nav-tabs.nav-justified > .active > a:focus {\n border-bottom-color: #ffffff;\n }\n}\n.nav-pills > li {\n float: left;\n}\n.nav-pills > li > a {\n border-radius: 4px;\n}\n.nav-pills > li + li {\n margin-left: 2px;\n}\n.nav-pills > li.active > a,\n.nav-pills > li.active > a:hover,\n.nav-pills > li.active > a:focus {\n color: #ffffff;\n background-color: #337ab7;\n}\n.nav-stacked > li {\n float: none;\n}\n.nav-stacked > li + li {\n margin-top: 2px;\n margin-left: 0;\n}\n.nav-justified {\n width: 100%;\n}\n.nav-justified > li {\n float: none;\n}\n.nav-justified > li > a {\n text-align: center;\n margin-bottom: 5px;\n}\n.nav-justified > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n}\n@media (min-width: 768px) {\n .nav-justified > li {\n display: table-cell;\n width: 1%;\n }\n .nav-justified > li > a {\n margin-bottom: 0;\n }\n}\n.nav-tabs-justified {\n border-bottom: 0;\n}\n.nav-tabs-justified > li > a {\n margin-right: 0;\n border-radius: 4px;\n}\n.nav-tabs-justified > .active > a,\n.nav-tabs-justified > .active > a:hover,\n.nav-tabs-justified > .active > a:focus {\n border: 1px solid #dddddd;\n}\n@media (min-width: 768px) {\n .nav-tabs-justified > li > a {\n border-bottom: 1px solid #dddddd;\n border-radius: 4px 4px 0 0;\n }\n .nav-tabs-justified > .active > a,\n .nav-tabs-justified > .active > a:hover,\n .nav-tabs-justified > .active > a:focus {\n border-bottom-color: #ffffff;\n }\n}\n.tab-content > .tab-pane {\n display: none;\n}\n.tab-content > .active {\n display: block;\n}\n.nav-tabs .dropdown-menu {\n margin-top: -1px;\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.navbar {\n position: relative;\n min-height: 50px;\n margin-bottom: 20px;\n border: 1px solid transparent;\n}\n@media (min-width: 768px) {\n .navbar {\n border-radius: 4px;\n }\n}\n@media (min-width: 768px) {\n .navbar-header {\n float: left;\n }\n}\n.navbar-collapse {\n overflow-x: visible;\n padding-right: 15px;\n padding-left: 15px;\n border-top: 1px solid transparent;\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);\n -webkit-overflow-scrolling: touch;\n}\n.navbar-collapse.in {\n overflow-y: auto;\n}\n@media (min-width: 768px) {\n .navbar-collapse {\n width: auto;\n border-top: 0;\n box-shadow: none;\n }\n .navbar-collapse.collapse {\n display: block !important;\n height: auto !important;\n padding-bottom: 0;\n overflow: visible !important;\n }\n .navbar-collapse.in {\n overflow-y: visible;\n }\n .navbar-fixed-top .navbar-collapse,\n .navbar-static-top .navbar-collapse,\n .navbar-fixed-bottom .navbar-collapse {\n padding-left: 0;\n padding-right: 0;\n }\n}\n.navbar-fixed-top .navbar-collapse,\n.navbar-fixed-bottom .navbar-collapse {\n max-height: 340px;\n}\n@media (max-device-width: 480px) and (orientation: landscape) {\n .navbar-fixed-top .navbar-collapse,\n .navbar-fixed-bottom .navbar-collapse {\n max-height: 200px;\n }\n}\n.container > .navbar-header,\n.container-fluid > .navbar-header,\n.container > .navbar-collapse,\n.container-fluid > .navbar-collapse {\n margin-right: -15px;\n margin-left: -15px;\n}\n@media (min-width: 768px) {\n .container > .navbar-header,\n .container-fluid > .navbar-header,\n .container > .navbar-collapse,\n .container-fluid > .navbar-collapse {\n margin-right: 0;\n margin-left: 0;\n }\n}\n.navbar-static-top {\n z-index: 1000;\n border-width: 0 0 1px;\n}\n@media (min-width: 768px) {\n .navbar-static-top {\n border-radius: 0;\n }\n}\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n position: fixed;\n right: 0;\n left: 0;\n z-index: 1030;\n}\n@media (min-width: 768px) {\n .navbar-fixed-top,\n .navbar-fixed-bottom {\n border-radius: 0;\n }\n}\n.navbar-fixed-top {\n top: 0;\n border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n bottom: 0;\n margin-bottom: 0;\n border-width: 1px 0 0;\n}\n.navbar-brand {\n float: left;\n padding: 15px 15px;\n font-size: 18px;\n line-height: 20px;\n height: 50px;\n}\n.navbar-brand:hover,\n.navbar-brand:focus {\n text-decoration: none;\n}\n.navbar-brand > img {\n display: block;\n}\n@media (min-width: 768px) {\n .navbar > .container .navbar-brand,\n .navbar > .container-fluid .navbar-brand {\n margin-left: -15px;\n }\n}\n.navbar-toggle {\n position: relative;\n float: right;\n margin-right: 15px;\n padding: 9px 10px;\n margin-top: 8px;\n margin-bottom: 8px;\n background-color: transparent;\n background-image: none;\n border: 1px solid transparent;\n border-radius: 4px;\n}\n.navbar-toggle:focus {\n outline: 0;\n}\n.navbar-toggle .icon-bar {\n display: block;\n width: 22px;\n height: 2px;\n border-radius: 1px;\n}\n.navbar-toggle .icon-bar + .icon-bar {\n margin-top: 4px;\n}\n@media (min-width: 768px) {\n .navbar-toggle {\n display: none;\n }\n}\n.navbar-nav {\n margin: 7.5px -15px;\n}\n.navbar-nav > li > a {\n padding-top: 10px;\n padding-bottom: 10px;\n line-height: 20px;\n}\n@media (max-width: 767px) {\n .navbar-nav .open .dropdown-menu {\n position: static;\n float: none;\n width: auto;\n margin-top: 0;\n background-color: transparent;\n border: 0;\n box-shadow: none;\n }\n .navbar-nav .open .dropdown-menu > li > a,\n .navbar-nav .open .dropdown-menu .dropdown-header {\n padding: 5px 15px 5px 25px;\n }\n .navbar-nav .open .dropdown-menu > li > a {\n line-height: 20px;\n }\n .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-nav .open .dropdown-menu > li > a:focus {\n background-image: none;\n }\n}\n@media (min-width: 768px) {\n .navbar-nav {\n float: left;\n margin: 0;\n }\n .navbar-nav > li {\n float: left;\n }\n .navbar-nav > li > a {\n padding-top: 15px;\n padding-bottom: 15px;\n }\n}\n.navbar-form {\n margin-left: -15px;\n margin-right: -15px;\n padding: 10px 15px;\n border-top: 1px solid transparent;\n border-bottom: 1px solid transparent;\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\n margin-top: 8px;\n margin-bottom: 8px;\n}\n@media (min-width: 768px) {\n .navbar-form .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .navbar-form .form-control-static {\n display: inline-block;\n }\n .navbar-form .input-group {\n display: inline-table;\n vertical-align: middle;\n }\n .navbar-form .input-group .input-group-addon,\n .navbar-form .input-group .input-group-btn,\n .navbar-form .input-group .form-control {\n width: auto;\n }\n .navbar-form .input-group > .form-control {\n width: 100%;\n }\n .navbar-form .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .radio,\n .navbar-form .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .radio label,\n .navbar-form .checkbox label {\n padding-left: 0;\n }\n .navbar-form .radio input[type=\"radio\"],\n .navbar-form .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n .navbar-form .has-feedback .form-control-feedback {\n top: 0;\n }\n}\n@media (max-width: 767px) {\n .navbar-form .form-group {\n margin-bottom: 5px;\n }\n .navbar-form .form-group:last-child {\n margin-bottom: 0;\n }\n}\n@media (min-width: 768px) {\n .navbar-form {\n width: auto;\n border: 0;\n margin-left: 0;\n margin-right: 0;\n padding-top: 0;\n padding-bottom: 0;\n -webkit-box-shadow: none;\n box-shadow: none;\n }\n}\n.navbar-nav > li > .dropdown-menu {\n margin-top: 0;\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n margin-bottom: 0;\n border-top-right-radius: 4px;\n border-top-left-radius: 4px;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.navbar-btn {\n margin-top: 8px;\n margin-bottom: 8px;\n}\n.navbar-btn.btn-sm {\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.navbar-btn.btn-xs {\n margin-top: 14px;\n margin-bottom: 14px;\n}\n.navbar-text {\n margin-top: 15px;\n margin-bottom: 15px;\n}\n@media (min-width: 768px) {\n .navbar-text {\n float: left;\n margin-left: 15px;\n margin-right: 15px;\n }\n}\n@media (min-width: 768px) {\n .navbar-left {\n float: left !important;\n }\n .navbar-right {\n float: right !important;\n margin-right: -15px;\n }\n .navbar-right ~ .navbar-right {\n margin-right: 0;\n }\n}\n.navbar-default {\n background-color: #f8f8f8;\n border-color: #e7e7e7;\n}\n.navbar-default .navbar-brand {\n color: #777777;\n}\n.navbar-default .navbar-brand:hover,\n.navbar-default .navbar-brand:focus {\n color: #5e5e5e;\n background-color: transparent;\n}\n.navbar-default .navbar-text {\n color: #777777;\n}\n.navbar-default .navbar-nav > li > a {\n color: #777777;\n}\n.navbar-default .navbar-nav > li > a:hover,\n.navbar-default .navbar-nav > li > a:focus {\n color: #333333;\n background-color: transparent;\n}\n.navbar-default .navbar-nav > .active > a,\n.navbar-default .navbar-nav > .active > a:hover,\n.navbar-default .navbar-nav > .active > a:focus {\n color: #555555;\n background-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .disabled > a,\n.navbar-default .navbar-nav > .disabled > a:hover,\n.navbar-default .navbar-nav > .disabled > a:focus {\n color: #cccccc;\n background-color: transparent;\n}\n.navbar-default .navbar-toggle {\n border-color: #dddddd;\n}\n.navbar-default .navbar-toggle:hover,\n.navbar-default .navbar-toggle:focus {\n background-color: #dddddd;\n}\n.navbar-default .navbar-toggle .icon-bar {\n background-color: #888888;\n}\n.navbar-default .navbar-collapse,\n.navbar-default .navbar-form {\n border-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .open > a:hover,\n.navbar-default .navbar-nav > .open > a:focus {\n background-color: #e7e7e7;\n color: #555555;\n}\n@media (max-width: 767px) {\n .navbar-default .navbar-nav .open .dropdown-menu > li > a {\n color: #777777;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {\n color: #333333;\n background-color: transparent;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a,\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #555555;\n background-color: #e7e7e7;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n color: #cccccc;\n background-color: transparent;\n }\n}\n.navbar-default .navbar-link {\n color: #777777;\n}\n.navbar-default .navbar-link:hover {\n color: #333333;\n}\n.navbar-default .btn-link {\n color: #777777;\n}\n.navbar-default .btn-link:hover,\n.navbar-default .btn-link:focus {\n color: #333333;\n}\n.navbar-default .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-default .btn-link:hover,\n.navbar-default .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-default .btn-link:focus {\n color: #cccccc;\n}\n.navbar-inverse {\n background-color: #222222;\n border-color: #080808;\n}\n.navbar-inverse .navbar-brand {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-brand:hover,\n.navbar-inverse .navbar-brand:focus {\n color: #ffffff;\n background-color: transparent;\n}\n.navbar-inverse .navbar-text {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a:hover,\n.navbar-inverse .navbar-nav > li > a:focus {\n color: #ffffff;\n background-color: transparent;\n}\n.navbar-inverse .navbar-nav > .active > a,\n.navbar-inverse .navbar-nav > .active > a:hover,\n.navbar-inverse .navbar-nav > .active > a:focus {\n color: #ffffff;\n background-color: #080808;\n}\n.navbar-inverse .navbar-nav > .disabled > a,\n.navbar-inverse .navbar-nav > .disabled > a:hover,\n.navbar-inverse .navbar-nav > .disabled > a:focus {\n color: #444444;\n background-color: transparent;\n}\n.navbar-inverse .navbar-toggle {\n border-color: #333333;\n}\n.navbar-inverse .navbar-toggle:hover,\n.navbar-inverse .navbar-toggle:focus {\n background-color: #333333;\n}\n.navbar-inverse .navbar-toggle .icon-bar {\n background-color: #ffffff;\n}\n.navbar-inverse .navbar-collapse,\n.navbar-inverse .navbar-form {\n border-color: #101010;\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .open > a:hover,\n.navbar-inverse .navbar-nav > .open > a:focus {\n background-color: #080808;\n color: #ffffff;\n}\n@media (max-width: 767px) {\n .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {\n border-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu .divider {\n background-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {\n color: #9d9d9d;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {\n color: #ffffff;\n background-color: transparent;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #ffffff;\n background-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n color: #444444;\n background-color: transparent;\n }\n}\n.navbar-inverse .navbar-link {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-link:hover {\n color: #ffffff;\n}\n.navbar-inverse .btn-link {\n color: #9d9d9d;\n}\n.navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link:focus {\n color: #ffffff;\n}\n.navbar-inverse .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-inverse .btn-link:focus {\n color: #444444;\n}\n.breadcrumb {\n padding: 8px 15px;\n margin-bottom: 20px;\n list-style: none;\n background-color: #f5f5f5;\n border-radius: 4px;\n}\n.breadcrumb > li {\n display: inline-block;\n}\n.breadcrumb > li + li:before {\n content: \"/\\00a0\";\n padding: 0 5px;\n color: #cccccc;\n}\n.breadcrumb > .active {\n color: #777777;\n}\n.pagination {\n display: inline-block;\n padding-left: 0;\n margin: 20px 0;\n border-radius: 4px;\n}\n.pagination > li {\n display: inline;\n}\n.pagination > li > a,\n.pagination > li > span {\n position: relative;\n float: left;\n padding: 6px 12px;\n line-height: 1.42857143;\n text-decoration: none;\n color: #337ab7;\n background-color: #ffffff;\n border: 1px solid #dddddd;\n margin-left: -1px;\n}\n.pagination > li:first-child > a,\n.pagination > li:first-child > span {\n margin-left: 0;\n border-bottom-left-radius: 4px;\n border-top-left-radius: 4px;\n}\n.pagination > li:last-child > a,\n.pagination > li:last-child > span {\n border-bottom-right-radius: 4px;\n border-top-right-radius: 4px;\n}\n.pagination > li > a:hover,\n.pagination > li > span:hover,\n.pagination > li > a:focus,\n.pagination > li > span:focus {\n color: #23527c;\n background-color: #eeeeee;\n border-color: #dddddd;\n}\n.pagination > .active > a,\n.pagination > .active > span,\n.pagination > .active > a:hover,\n.pagination > .active > span:hover,\n.pagination > .active > a:focus,\n.pagination > .active > span:focus {\n z-index: 2;\n color: #ffffff;\n background-color: #337ab7;\n border-color: #337ab7;\n cursor: default;\n}\n.pagination > .disabled > span,\n.pagination > .disabled > span:hover,\n.pagination > .disabled > span:focus,\n.pagination > .disabled > a,\n.pagination > .disabled > a:hover,\n.pagination > .disabled > a:focus {\n color: #777777;\n background-color: #ffffff;\n border-color: #dddddd;\n cursor: not-allowed;\n}\n.pagination-lg > li > a,\n.pagination-lg > li > span {\n padding: 10px 16px;\n font-size: 18px;\n}\n.pagination-lg > li:first-child > a,\n.pagination-lg > li:first-child > span {\n border-bottom-left-radius: 6px;\n border-top-left-radius: 6px;\n}\n.pagination-lg > li:last-child > a,\n.pagination-lg > li:last-child > span {\n border-bottom-right-radius: 6px;\n border-top-right-radius: 6px;\n}\n.pagination-sm > li > a,\n.pagination-sm > li > span {\n padding: 5px 10px;\n font-size: 12px;\n}\n.pagination-sm > li:first-child > a,\n.pagination-sm > li:first-child > span {\n border-bottom-left-radius: 3px;\n border-top-left-radius: 3px;\n}\n.pagination-sm > li:last-child > a,\n.pagination-sm > li:last-child > span {\n border-bottom-right-radius: 3px;\n border-top-right-radius: 3px;\n}\n.pager {\n padding-left: 0;\n margin: 20px 0;\n list-style: none;\n text-align: center;\n}\n.pager li {\n display: inline;\n}\n.pager li > a,\n.pager li > span {\n display: inline-block;\n padding: 5px 14px;\n background-color: #ffffff;\n border: 1px solid #dddddd;\n border-radius: 15px;\n}\n.pager li > a:hover,\n.pager li > a:focus {\n text-decoration: none;\n background-color: #eeeeee;\n}\n.pager .next > a,\n.pager .next > span {\n float: right;\n}\n.pager .previous > a,\n.pager .previous > span {\n float: left;\n}\n.pager .disabled > a,\n.pager .disabled > a:hover,\n.pager .disabled > a:focus,\n.pager .disabled > span {\n color: #777777;\n background-color: #ffffff;\n cursor: not-allowed;\n}\n.label {\n display: inline;\n padding: .2em .6em .3em;\n font-size: 75%;\n font-weight: bold;\n line-height: 1;\n color: #ffffff;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: .25em;\n}\na.label:hover,\na.label:focus {\n color: #ffffff;\n text-decoration: none;\n cursor: pointer;\n}\n.label:empty {\n display: none;\n}\n.btn .label {\n position: relative;\n top: -1px;\n}\n.label-default {\n background-color: #777777;\n}\n.label-default[href]:hover,\n.label-default[href]:focus {\n background-color: #5e5e5e;\n}\n.label-primary {\n background-color: #337ab7;\n}\n.label-primary[href]:hover,\n.label-primary[href]:focus {\n background-color: #286090;\n}\n.label-success {\n background-color: #5cb85c;\n}\n.label-success[href]:hover,\n.label-success[href]:focus {\n background-color: #449d44;\n}\n.label-info {\n background-color: #5bc0de;\n}\n.label-info[href]:hover,\n.label-info[href]:focus {\n background-color: #31b0d5;\n}\n.label-warning {\n background-color: #f0ad4e;\n}\n.label-warning[href]:hover,\n.label-warning[href]:focus {\n background-color: #ec971f;\n}\n.label-danger {\n background-color: #d9534f;\n}\n.label-danger[href]:hover,\n.label-danger[href]:focus {\n background-color: #c9302c;\n}\n.badge {\n display: inline-block;\n min-width: 10px;\n padding: 3px 7px;\n font-size: 12px;\n font-weight: bold;\n color: #ffffff;\n line-height: 1;\n vertical-align: baseline;\n white-space: nowrap;\n text-align: center;\n background-color: #777777;\n border-radius: 10px;\n}\n.badge:empty {\n display: none;\n}\n.btn .badge {\n position: relative;\n top: -1px;\n}\n.btn-xs .badge,\n.btn-group-xs > .btn .badge {\n top: 0;\n padding: 1px 5px;\n}\na.badge:hover,\na.badge:focus {\n color: #ffffff;\n text-decoration: none;\n cursor: pointer;\n}\n.list-group-item.active > .badge,\n.nav-pills > .active > a > .badge {\n color: #337ab7;\n background-color: #ffffff;\n}\n.list-group-item > .badge {\n float: right;\n}\n.list-group-item > .badge + .badge {\n margin-right: 5px;\n}\n.nav-pills > li > a > .badge {\n margin-left: 3px;\n}\n.jumbotron {\n padding: 30px 15px;\n margin-bottom: 30px;\n color: inherit;\n background-color: #eeeeee;\n}\n.jumbotron h1,\n.jumbotron .h1 {\n color: inherit;\n}\n.jumbotron p {\n margin-bottom: 15px;\n font-size: 21px;\n font-weight: 200;\n}\n.jumbotron > hr {\n border-top-color: #d5d5d5;\n}\n.container .jumbotron,\n.container-fluid .jumbotron {\n border-radius: 6px;\n}\n.jumbotron .container {\n max-width: 100%;\n}\n@media screen and (min-width: 768px) {\n .jumbotron {\n padding: 48px 0;\n }\n .container .jumbotron,\n .container-fluid .jumbotron {\n padding-left: 60px;\n padding-right: 60px;\n }\n .jumbotron h1,\n .jumbotron .h1 {\n font-size: 63px;\n }\n}\n.thumbnail {\n display: block;\n padding: 4px;\n margin-bottom: 20px;\n line-height: 1.42857143;\n background-color: #ffffff;\n border: 1px solid #dddddd;\n border-radius: 4px;\n -webkit-transition: border 0.2s ease-in-out;\n -o-transition: border 0.2s ease-in-out;\n transition: border 0.2s ease-in-out;\n}\n.thumbnail > img,\n.thumbnail a > img {\n margin-left: auto;\n margin-right: auto;\n}\na.thumbnail:hover,\na.thumbnail:focus,\na.thumbnail.active {\n border-color: #337ab7;\n}\n.thumbnail .caption {\n padding: 9px;\n color: #333333;\n}\n.alert {\n padding: 15px;\n margin-bottom: 20px;\n border: 1px solid transparent;\n border-radius: 4px;\n}\n.alert h4 {\n margin-top: 0;\n color: inherit;\n}\n.alert .alert-link {\n font-weight: bold;\n}\n.alert > p,\n.alert > ul {\n margin-bottom: 0;\n}\n.alert > p + p {\n margin-top: 5px;\n}\n.alert-dismissable,\n.alert-dismissible {\n padding-right: 35px;\n}\n.alert-dismissable .close,\n.alert-dismissible .close {\n position: relative;\n top: -2px;\n right: -21px;\n color: inherit;\n}\n.alert-success {\n background-color: #dff0d8;\n border-color: #d6e9c6;\n color: #3c763d;\n}\n.alert-success hr {\n border-top-color: #c9e2b3;\n}\n.alert-success .alert-link {\n color: #2b542c;\n}\n.alert-info {\n background-color: #d9edf7;\n border-color: #bce8f1;\n color: #31708f;\n}\n.alert-info hr {\n border-top-color: #a6e1ec;\n}\n.alert-info .alert-link {\n color: #245269;\n}\n.alert-warning {\n background-color: #fcf8e3;\n border-color: #faebcc;\n color: #8a6d3b;\n}\n.alert-warning hr {\n border-top-color: #f7e1b5;\n}\n.alert-warning .alert-link {\n color: #66512c;\n}\n.alert-danger {\n background-color: #f2dede;\n border-color: #ebccd1;\n color: #a94442;\n}\n.alert-danger hr {\n border-top-color: #e4b9c0;\n}\n.alert-danger .alert-link {\n color: #843534;\n}\n@-webkit-keyframes progress-bar-stripes {\n from {\n background-position: 40px 0;\n }\n to {\n background-position: 0 0;\n }\n}\n@keyframes progress-bar-stripes {\n from {\n background-position: 40px 0;\n }\n to {\n background-position: 0 0;\n }\n}\n.progress {\n overflow: hidden;\n height: 20px;\n margin-bottom: 20px;\n background-color: #f5f5f5;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n}\n.progress-bar {\n float: left;\n width: 0%;\n height: 100%;\n font-size: 12px;\n line-height: 20px;\n color: #ffffff;\n text-align: center;\n background-color: #337ab7;\n -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n -webkit-transition: width 0.6s ease;\n -o-transition: width 0.6s ease;\n transition: width 0.6s ease;\n}\n.progress-striped .progress-bar,\n.progress-bar-striped {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-size: 40px 40px;\n}\n.progress.active .progress-bar,\n.progress-bar.active {\n -webkit-animation: progress-bar-stripes 2s linear infinite;\n -o-animation: progress-bar-stripes 2s linear infinite;\n animation: progress-bar-stripes 2s linear infinite;\n}\n.progress-bar-success {\n background-color: #5cb85c;\n}\n.progress-striped .progress-bar-success {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-info {\n background-color: #5bc0de;\n}\n.progress-striped .progress-bar-info {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-warning {\n background-color: #f0ad4e;\n}\n.progress-striped .progress-bar-warning {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-danger {\n background-color: #d9534f;\n}\n.progress-striped .progress-bar-danger {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.media {\n margin-top: 15px;\n}\n.media:first-child {\n margin-top: 0;\n}\n.media,\n.media-body {\n zoom: 1;\n overflow: hidden;\n}\n.media-body {\n width: 10000px;\n}\n.media-object {\n display: block;\n}\n.media-right,\n.media > .pull-right {\n padding-left: 10px;\n}\n.media-left,\n.media > .pull-left {\n padding-right: 10px;\n}\n.media-left,\n.media-right,\n.media-body {\n display: table-cell;\n vertical-align: top;\n}\n.media-middle {\n vertical-align: middle;\n}\n.media-bottom {\n vertical-align: bottom;\n}\n.media-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.media-list {\n padding-left: 0;\n list-style: none;\n}\n.list-group {\n margin-bottom: 20px;\n padding-left: 0;\n}\n.list-group-item {\n position: relative;\n display: block;\n padding: 10px 15px;\n margin-bottom: -1px;\n background-color: #ffffff;\n border: 1px solid #dddddd;\n}\n.list-group-item:first-child {\n border-top-right-radius: 4px;\n border-top-left-radius: 4px;\n}\n.list-group-item:last-child {\n margin-bottom: 0;\n border-bottom-right-radius: 4px;\n border-bottom-left-radius: 4px;\n}\na.list-group-item {\n color: #555555;\n}\na.list-group-item .list-group-item-heading {\n color: #333333;\n}\na.list-group-item:hover,\na.list-group-item:focus {\n text-decoration: none;\n color: #555555;\n background-color: #f5f5f5;\n}\n.list-group-item.disabled,\n.list-group-item.disabled:hover,\n.list-group-item.disabled:focus {\n background-color: #eeeeee;\n color: #777777;\n cursor: not-allowed;\n}\n.list-group-item.disabled .list-group-item-heading,\n.list-group-item.disabled:hover .list-group-item-heading,\n.list-group-item.disabled:focus .list-group-item-heading {\n color: inherit;\n}\n.list-group-item.disabled .list-group-item-text,\n.list-group-item.disabled:hover .list-group-item-text,\n.list-group-item.disabled:focus .list-group-item-text {\n color: #777777;\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n z-index: 2;\n color: #ffffff;\n background-color: #337ab7;\n border-color: #337ab7;\n}\n.list-group-item.active .list-group-item-heading,\n.list-group-item.active:hover .list-group-item-heading,\n.list-group-item.active:focus .list-group-item-heading,\n.list-group-item.active .list-group-item-heading > small,\n.list-group-item.active:hover .list-group-item-heading > small,\n.list-group-item.active:focus .list-group-item-heading > small,\n.list-group-item.active .list-group-item-heading > .small,\n.list-group-item.active:hover .list-group-item-heading > .small,\n.list-group-item.active:focus .list-group-item-heading > .small {\n color: inherit;\n}\n.list-group-item.active .list-group-item-text,\n.list-group-item.active:hover .list-group-item-text,\n.list-group-item.active:focus .list-group-item-text {\n color: #c7ddef;\n}\n.list-group-item-success {\n color: #3c763d;\n background-color: #dff0d8;\n}\na.list-group-item-success {\n color: #3c763d;\n}\na.list-group-item-success .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-success:hover,\na.list-group-item-success:focus {\n color: #3c763d;\n background-color: #d0e9c6;\n}\na.list-group-item-success.active,\na.list-group-item-success.active:hover,\na.list-group-item-success.active:focus {\n color: #fff;\n background-color: #3c763d;\n border-color: #3c763d;\n}\n.list-group-item-info {\n color: #31708f;\n background-color: #d9edf7;\n}\na.list-group-item-info {\n color: #31708f;\n}\na.list-group-item-info .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-info:hover,\na.list-group-item-info:focus {\n color: #31708f;\n background-color: #c4e3f3;\n}\na.list-group-item-info.active,\na.list-group-item-info.active:hover,\na.list-group-item-info.active:focus {\n color: #fff;\n background-color: #31708f;\n border-color: #31708f;\n}\n.list-group-item-warning {\n color: #8a6d3b;\n background-color: #fcf8e3;\n}\na.list-group-item-warning {\n color: #8a6d3b;\n}\na.list-group-item-warning .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-warning:hover,\na.list-group-item-warning:focus {\n color: #8a6d3b;\n background-color: #faf2cc;\n}\na.list-group-item-warning.active,\na.list-group-item-warning.active:hover,\na.list-group-item-warning.active:focus {\n color: #fff;\n background-color: #8a6d3b;\n border-color: #8a6d3b;\n}\n.list-group-item-danger {\n color: #a94442;\n background-color: #f2dede;\n}\na.list-group-item-danger {\n color: #a94442;\n}\na.list-group-item-danger .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-danger:hover,\na.list-group-item-danger:focus {\n color: #a94442;\n background-color: #ebcccc;\n}\na.list-group-item-danger.active,\na.list-group-item-danger.active:hover,\na.list-group-item-danger.active:focus {\n color: #fff;\n background-color: #a94442;\n border-color: #a94442;\n}\n.list-group-item-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.list-group-item-text {\n margin-bottom: 0;\n line-height: 1.3;\n}\n.panel {\n margin-bottom: 20px;\n background-color: #ffffff;\n border: 1px solid transparent;\n border-radius: 4px;\n -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);\n box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);\n}\n.panel-body {\n padding: 15px;\n}\n.panel-heading {\n padding: 10px 15px;\n border-bottom: 1px solid transparent;\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.panel-heading > .dropdown .dropdown-toggle {\n color: inherit;\n}\n.panel-title {\n margin-top: 0;\n margin-bottom: 0;\n font-size: 16px;\n color: inherit;\n}\n.panel-title > a,\n.panel-title > small,\n.panel-title > .small,\n.panel-title > small > a,\n.panel-title > .small > a {\n color: inherit;\n}\n.panel-footer {\n padding: 10px 15px;\n background-color: #f5f5f5;\n border-top: 1px solid #dddddd;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .list-group,\n.panel > .panel-collapse > .list-group {\n margin-bottom: 0;\n}\n.panel > .list-group .list-group-item,\n.panel > .panel-collapse > .list-group .list-group-item {\n border-width: 1px 0;\n border-radius: 0;\n}\n.panel > .list-group:first-child .list-group-item:first-child,\n.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child {\n border-top: 0;\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.panel > .list-group:last-child .list-group-item:last-child,\n.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child {\n border-bottom: 0;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel-heading + .list-group .list-group-item:first-child {\n border-top-width: 0;\n}\n.list-group + .panel-footer {\n border-top-width: 0;\n}\n.panel > .table,\n.panel > .table-responsive > .table,\n.panel > .panel-collapse > .table {\n margin-bottom: 0;\n}\n.panel > .table caption,\n.panel > .table-responsive > .table caption,\n.panel > .panel-collapse > .table caption {\n padding-left: 15px;\n padding-right: 15px;\n}\n.panel > .table:first-child,\n.panel > .table-responsive:first-child > .table:first-child {\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child {\n border-top-left-radius: 3px;\n border-top-right-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child {\n border-top-left-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child {\n border-top-right-radius: 3px;\n}\n.panel > .table:last-child,\n.panel > .table-responsive:last-child > .table:last-child {\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child {\n border-bottom-left-radius: 3px;\n border-bottom-right-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child {\n border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child {\n border-bottom-right-radius: 3px;\n}\n.panel > .panel-body + .table,\n.panel > .panel-body + .table-responsive,\n.panel > .table + .panel-body,\n.panel > .table-responsive + .panel-body {\n border-top: 1px solid #dddddd;\n}\n.panel > .table > tbody:first-child > tr:first-child th,\n.panel > .table > tbody:first-child > tr:first-child td {\n border-top: 0;\n}\n.panel > .table-bordered,\n.panel > .table-responsive > .table-bordered {\n border: 0;\n}\n.panel > .table-bordered > thead > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:first-child,\n.panel > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-bordered > thead > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:first-child,\n.panel > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-bordered > tfoot > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n border-left: 0;\n}\n.panel > .table-bordered > thead > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:last-child,\n.panel > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-bordered > thead > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:last-child,\n.panel > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-bordered > tfoot > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n border-right: 0;\n}\n.panel > .table-bordered > thead > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > td,\n.panel > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-bordered > thead > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > th,\n.panel > .table-bordered > tbody > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th {\n border-bottom: 0;\n}\n.panel > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-bordered > tfoot > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th {\n border-bottom: 0;\n}\n.panel > .table-responsive {\n border: 0;\n margin-bottom: 0;\n}\n.panel-group {\n margin-bottom: 20px;\n}\n.panel-group .panel {\n margin-bottom: 0;\n border-radius: 4px;\n}\n.panel-group .panel + .panel {\n margin-top: 5px;\n}\n.panel-group .panel-heading {\n border-bottom: 0;\n}\n.panel-group .panel-heading + .panel-collapse > .panel-body,\n.panel-group .panel-heading + .panel-collapse > .list-group {\n border-top: 1px solid #dddddd;\n}\n.panel-group .panel-footer {\n border-top: 0;\n}\n.panel-group .panel-footer + .panel-collapse .panel-body {\n border-bottom: 1px solid #dddddd;\n}\n.panel-default {\n border-color: #dddddd;\n}\n.panel-default > .panel-heading {\n color: #333333;\n background-color: #f5f5f5;\n border-color: #dddddd;\n}\n.panel-default > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #dddddd;\n}\n.panel-default > .panel-heading .badge {\n color: #f5f5f5;\n background-color: #333333;\n}\n.panel-default > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #dddddd;\n}\n.panel-primary {\n border-color: #337ab7;\n}\n.panel-primary > .panel-heading {\n color: #ffffff;\n background-color: #337ab7;\n border-color: #337ab7;\n}\n.panel-primary > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #337ab7;\n}\n.panel-primary > .panel-heading .badge {\n color: #337ab7;\n background-color: #ffffff;\n}\n.panel-primary > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #337ab7;\n}\n.panel-success {\n border-color: #d6e9c6;\n}\n.panel-success > .panel-heading {\n color: #3c763d;\n background-color: #dff0d8;\n border-color: #d6e9c6;\n}\n.panel-success > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #d6e9c6;\n}\n.panel-success > .panel-heading .badge {\n color: #dff0d8;\n background-color: #3c763d;\n}\n.panel-success > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #d6e9c6;\n}\n.panel-info {\n border-color: #bce8f1;\n}\n.panel-info > .panel-heading {\n color: #31708f;\n background-color: #d9edf7;\n border-color: #bce8f1;\n}\n.panel-info > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #bce8f1;\n}\n.panel-info > .panel-heading .badge {\n color: #d9edf7;\n background-color: #31708f;\n}\n.panel-info > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #bce8f1;\n}\n.panel-warning {\n border-color: #faebcc;\n}\n.panel-warning > .panel-heading {\n color: #8a6d3b;\n background-color: #fcf8e3;\n border-color: #faebcc;\n}\n.panel-warning > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #faebcc;\n}\n.panel-warning > .panel-heading .badge {\n color: #fcf8e3;\n background-color: #8a6d3b;\n}\n.panel-warning > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #faebcc;\n}\n.panel-danger {\n border-color: #ebccd1;\n}\n.panel-danger > .panel-heading {\n color: #a94442;\n background-color: #f2dede;\n border-color: #ebccd1;\n}\n.panel-danger > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #ebccd1;\n}\n.panel-danger > .panel-heading .badge {\n color: #f2dede;\n background-color: #a94442;\n}\n.panel-danger > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #ebccd1;\n}\n.embed-responsive {\n position: relative;\n display: block;\n height: 0;\n padding: 0;\n overflow: hidden;\n}\n.embed-responsive .embed-responsive-item,\n.embed-responsive iframe,\n.embed-responsive embed,\n.embed-responsive object,\n.embed-responsive video {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n height: 100%;\n width: 100%;\n border: 0;\n}\n.embed-responsive-16by9 {\n padding-bottom: 56.25%;\n}\n.embed-responsive-4by3 {\n padding-bottom: 75%;\n}\n.well {\n min-height: 20px;\n padding: 19px;\n margin-bottom: 20px;\n background-color: #f5f5f5;\n border: 1px solid #e3e3e3;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n}\n.well blockquote {\n border-color: #ddd;\n border-color: rgba(0, 0, 0, 0.15);\n}\n.well-lg {\n padding: 24px;\n border-radius: 6px;\n}\n.well-sm {\n padding: 9px;\n border-radius: 3px;\n}\n.close {\n float: right;\n font-size: 21px;\n font-weight: bold;\n line-height: 1;\n color: #000000;\n text-shadow: 0 1px 0 #ffffff;\n opacity: 0.2;\n filter: alpha(opacity=20);\n}\n.close:hover,\n.close:focus {\n color: #000000;\n text-decoration: none;\n cursor: pointer;\n opacity: 0.5;\n filter: alpha(opacity=50);\n}\nbutton.close {\n padding: 0;\n cursor: pointer;\n background: transparent;\n border: 0;\n -webkit-appearance: none;\n}\n.modal-open {\n overflow: hidden;\n}\n.modal {\n display: none;\n overflow: hidden;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1050;\n -webkit-overflow-scrolling: touch;\n outline: 0;\n}\n.modal.fade .modal-dialog {\n -webkit-transform: translate(0, -25%);\n -ms-transform: translate(0, -25%);\n -o-transform: translate(0, -25%);\n transform: translate(0, -25%);\n -webkit-transition: -webkit-transform 0.3s ease-out;\n -moz-transition: -moz-transform 0.3s ease-out;\n -o-transition: -o-transform 0.3s ease-out;\n transition: transform 0.3s ease-out;\n}\n.modal.in .modal-dialog {\n -webkit-transform: translate(0, 0);\n -ms-transform: translate(0, 0);\n -o-transform: translate(0, 0);\n transform: translate(0, 0);\n}\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 10px;\n}\n.modal-content {\n position: relative;\n background-color: #ffffff;\n border: 1px solid #999999;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 6px;\n -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);\n box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);\n background-clip: padding-box;\n outline: 0;\n}\n.modal-backdrop {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1040;\n background-color: #000000;\n}\n.modal-backdrop.fade {\n opacity: 0;\n filter: alpha(opacity=0);\n}\n.modal-backdrop.in {\n opacity: 0.5;\n filter: alpha(opacity=50);\n}\n.modal-header {\n padding: 15px;\n border-bottom: 1px solid #e5e5e5;\n min-height: 16.42857143px;\n}\n.modal-header .close {\n margin-top: -2px;\n}\n.modal-title {\n margin: 0;\n line-height: 1.42857143;\n}\n.modal-body {\n position: relative;\n padding: 15px;\n}\n.modal-footer {\n padding: 15px;\n text-align: right;\n border-top: 1px solid #e5e5e5;\n}\n.modal-footer .btn + .btn {\n margin-left: 5px;\n margin-bottom: 0;\n}\n.modal-footer .btn-group .btn + .btn {\n margin-left: -1px;\n}\n.modal-footer .btn-block + .btn-block {\n margin-left: 0;\n}\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n@media (min-width: 768px) {\n .modal-dialog {\n width: 600px;\n margin: 30px auto;\n }\n .modal-content {\n -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);\n box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);\n }\n .modal-sm {\n width: 300px;\n }\n}\n@media (min-width: 992px) {\n .modal-lg {\n width: 900px;\n }\n}\n.tooltip {\n position: absolute;\n z-index: 1070;\n display: block;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-size: 12px;\n font-weight: normal;\n line-height: 1.4;\n opacity: 0;\n filter: alpha(opacity=0);\n}\n.tooltip.in {\n opacity: 0.9;\n filter: alpha(opacity=90);\n}\n.tooltip.top {\n margin-top: -3px;\n padding: 5px 0;\n}\n.tooltip.right {\n margin-left: 3px;\n padding: 0 5px;\n}\n.tooltip.bottom {\n margin-top: 3px;\n padding: 5px 0;\n}\n.tooltip.left {\n margin-left: -3px;\n padding: 0 5px;\n}\n.tooltip-inner {\n max-width: 200px;\n padding: 3px 8px;\n color: #ffffff;\n text-align: center;\n text-decoration: none;\n background-color: #000000;\n border-radius: 4px;\n}\n.tooltip-arrow {\n position: absolute;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n.tooltip.top .tooltip-arrow {\n bottom: 0;\n left: 50%;\n margin-left: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000000;\n}\n.tooltip.top-left .tooltip-arrow {\n bottom: 0;\n right: 5px;\n margin-bottom: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000000;\n}\n.tooltip.top-right .tooltip-arrow {\n bottom: 0;\n left: 5px;\n margin-bottom: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000000;\n}\n.tooltip.right .tooltip-arrow {\n top: 50%;\n left: 0;\n margin-top: -5px;\n border-width: 5px 5px 5px 0;\n border-right-color: #000000;\n}\n.tooltip.left .tooltip-arrow {\n top: 50%;\n right: 0;\n margin-top: -5px;\n border-width: 5px 0 5px 5px;\n border-left-color: #000000;\n}\n.tooltip.bottom .tooltip-arrow {\n top: 0;\n left: 50%;\n margin-left: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000000;\n}\n.tooltip.bottom-left .tooltip-arrow {\n top: 0;\n right: 5px;\n margin-top: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000000;\n}\n.tooltip.bottom-right .tooltip-arrow {\n top: 0;\n left: 5px;\n margin-top: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000000;\n}\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1060;\n display: none;\n max-width: 276px;\n padding: 1px;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-size: 14px;\n font-weight: normal;\n line-height: 1.42857143;\n text-align: left;\n background-color: #ffffff;\n background-clip: padding-box;\n border: 1px solid #cccccc;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 6px;\n -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n white-space: normal;\n}\n.popover.top {\n margin-top: -10px;\n}\n.popover.right {\n margin-left: 10px;\n}\n.popover.bottom {\n margin-top: 10px;\n}\n.popover.left {\n margin-left: -10px;\n}\n.popover-title {\n margin: 0;\n padding: 8px 14px;\n font-size: 14px;\n background-color: #f7f7f7;\n border-bottom: 1px solid #ebebeb;\n border-radius: 5px 5px 0 0;\n}\n.popover-content {\n padding: 9px 14px;\n}\n.popover > .arrow,\n.popover > .arrow:after {\n position: absolute;\n display: block;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n.popover > .arrow {\n border-width: 11px;\n}\n.popover > .arrow:after {\n border-width: 10px;\n content: \"\";\n}\n.popover.top > .arrow {\n left: 50%;\n margin-left: -11px;\n border-bottom-width: 0;\n border-top-color: #999999;\n border-top-color: rgba(0, 0, 0, 0.25);\n bottom: -11px;\n}\n.popover.top > .arrow:after {\n content: \" \";\n bottom: 1px;\n margin-left: -10px;\n border-bottom-width: 0;\n border-top-color: #ffffff;\n}\n.popover.right > .arrow {\n top: 50%;\n left: -11px;\n margin-top: -11px;\n border-left-width: 0;\n border-right-color: #999999;\n border-right-color: rgba(0, 0, 0, 0.25);\n}\n.popover.right > .arrow:after {\n content: \" \";\n left: 1px;\n bottom: -10px;\n border-left-width: 0;\n border-right-color: #ffffff;\n}\n.popover.bottom > .arrow {\n left: 50%;\n margin-left: -11px;\n border-top-width: 0;\n border-bottom-color: #999999;\n border-bottom-color: rgba(0, 0, 0, 0.25);\n top: -11px;\n}\n.popover.bottom > .arrow:after {\n content: \" \";\n top: 1px;\n margin-left: -10px;\n border-top-width: 0;\n border-bottom-color: #ffffff;\n}\n.popover.left > .arrow {\n top: 50%;\n right: -11px;\n margin-top: -11px;\n border-right-width: 0;\n border-left-color: #999999;\n border-left-color: rgba(0, 0, 0, 0.25);\n}\n.popover.left > .arrow:after {\n content: \" \";\n right: 1px;\n border-right-width: 0;\n border-left-color: #ffffff;\n bottom: -10px;\n}\n.carousel {\n position: relative;\n}\n.carousel-inner {\n position: relative;\n overflow: hidden;\n width: 100%;\n}\n.carousel-inner > .item {\n display: none;\n position: relative;\n -webkit-transition: 0.6s ease-in-out left;\n -o-transition: 0.6s ease-in-out left;\n transition: 0.6s ease-in-out left;\n}\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n line-height: 1;\n}\n@media all and (transform-3d), (-webkit-transform-3d) {\n .carousel-inner > .item {\n -webkit-transition: -webkit-transform 0.6s ease-in-out;\n -moz-transition: -moz-transform 0.6s ease-in-out;\n -o-transition: -o-transform 0.6s ease-in-out;\n transition: transform 0.6s ease-in-out;\n -webkit-backface-visibility: hidden;\n -moz-backface-visibility: hidden;\n backface-visibility: hidden;\n -webkit-perspective: 1000;\n -moz-perspective: 1000;\n perspective: 1000;\n }\n .carousel-inner > .item.next,\n .carousel-inner > .item.active.right {\n -webkit-transform: translate3d(100%, 0, 0);\n transform: translate3d(100%, 0, 0);\n left: 0;\n }\n .carousel-inner > .item.prev,\n .carousel-inner > .item.active.left {\n -webkit-transform: translate3d(-100%, 0, 0);\n transform: translate3d(-100%, 0, 0);\n left: 0;\n }\n .carousel-inner > .item.next.left,\n .carousel-inner > .item.prev.right,\n .carousel-inner > .item.active {\n -webkit-transform: translate3d(0, 0, 0);\n transform: translate3d(0, 0, 0);\n left: 0;\n }\n}\n.carousel-inner > .active,\n.carousel-inner > .next,\n.carousel-inner > .prev {\n display: block;\n}\n.carousel-inner > .active {\n left: 0;\n}\n.carousel-inner > .next,\n.carousel-inner > .prev {\n position: absolute;\n top: 0;\n width: 100%;\n}\n.carousel-inner > .next {\n left: 100%;\n}\n.carousel-inner > .prev {\n left: -100%;\n}\n.carousel-inner > .next.left,\n.carousel-inner > .prev.right {\n left: 0;\n}\n.carousel-inner > .active.left {\n left: -100%;\n}\n.carousel-inner > .active.right {\n left: 100%;\n}\n.carousel-control {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n width: 15%;\n opacity: 0.5;\n filter: alpha(opacity=50);\n font-size: 20px;\n color: #ffffff;\n text-align: center;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n}\n.carousel-control.left {\n background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);\n}\n.carousel-control.right {\n left: auto;\n right: 0;\n background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);\n}\n.carousel-control:hover,\n.carousel-control:focus {\n outline: 0;\n color: #ffffff;\n text-decoration: none;\n opacity: 0.9;\n filter: alpha(opacity=90);\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-left,\n.carousel-control .glyphicon-chevron-right {\n position: absolute;\n top: 50%;\n z-index: 5;\n display: inline-block;\n}\n.carousel-control .icon-prev,\n.carousel-control .glyphicon-chevron-left {\n left: 50%;\n margin-left: -10px;\n}\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-right {\n right: 50%;\n margin-right: -10px;\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next {\n width: 20px;\n height: 20px;\n margin-top: -10px;\n line-height: 1;\n font-family: serif;\n}\n.carousel-control .icon-prev:before {\n content: '\\2039';\n}\n.carousel-control .icon-next:before {\n content: '\\203a';\n}\n.carousel-indicators {\n position: absolute;\n bottom: 10px;\n left: 50%;\n z-index: 15;\n width: 60%;\n margin-left: -30%;\n padding-left: 0;\n list-style: none;\n text-align: center;\n}\n.carousel-indicators li {\n display: inline-block;\n width: 10px;\n height: 10px;\n margin: 1px;\n text-indent: -999px;\n border: 1px solid #ffffff;\n border-radius: 10px;\n cursor: pointer;\n background-color: #000 \\9;\n background-color: rgba(0, 0, 0, 0);\n}\n.carousel-indicators .active {\n margin: 0;\n width: 12px;\n height: 12px;\n background-color: #ffffff;\n}\n.carousel-caption {\n position: absolute;\n left: 15%;\n right: 15%;\n bottom: 20px;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: #ffffff;\n text-align: center;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n}\n.carousel-caption .btn {\n text-shadow: none;\n}\n@media screen and (min-width: 768px) {\n .carousel-control .glyphicon-chevron-left,\n .carousel-control .glyphicon-chevron-right,\n .carousel-control .icon-prev,\n .carousel-control .icon-next {\n width: 30px;\n height: 30px;\n margin-top: -15px;\n font-size: 30px;\n }\n .carousel-control .glyphicon-chevron-left,\n .carousel-control .icon-prev {\n margin-left: -15px;\n }\n .carousel-control .glyphicon-chevron-right,\n .carousel-control .icon-next {\n margin-right: -15px;\n }\n .carousel-caption {\n left: 20%;\n right: 20%;\n padding-bottom: 30px;\n }\n .carousel-indicators {\n bottom: 20px;\n }\n}\n.clearfix:before,\n.clearfix:after,\n.dl-horizontal dd:before,\n.dl-horizontal dd:after,\n.container:before,\n.container:after,\n.container-fluid:before,\n.container-fluid:after,\n.row:before,\n.row:after,\n.form-horizontal .form-group:before,\n.form-horizontal .form-group:after,\n.btn-toolbar:before,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:before,\n.btn-group-vertical > .btn-group:after,\n.nav:before,\n.nav:after,\n.navbar:before,\n.navbar:after,\n.navbar-header:before,\n.navbar-header:after,\n.navbar-collapse:before,\n.navbar-collapse:after,\n.pager:before,\n.pager:after,\n.panel-body:before,\n.panel-body:after,\n.modal-footer:before,\n.modal-footer:after {\n content: \" \";\n display: table;\n}\n.clearfix:after,\n.dl-horizontal dd:after,\n.container:after,\n.container-fluid:after,\n.row:after,\n.form-horizontal .form-group:after,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:after,\n.nav:after,\n.navbar:after,\n.navbar-header:after,\n.navbar-collapse:after,\n.pager:after,\n.panel-body:after,\n.modal-footer:after {\n clear: both;\n}\n.center-block {\n display: block;\n margin-left: auto;\n margin-right: auto;\n}\n.pull-right {\n float: right !important;\n}\n.pull-left {\n float: left !important;\n}\n.hide {\n display: none !important;\n}\n.show {\n display: block !important;\n}\n.invisible {\n visibility: hidden;\n}\n.text-hide {\n font: 0/0 a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n.hidden {\n display: none !important;\n}\n.affix {\n position: fixed;\n}\n@-ms-viewport {\n width: device-width;\n}\n.visible-xs,\n.visible-sm,\n.visible-md,\n.visible-lg {\n display: none !important;\n}\n.visible-xs-block,\n.visible-xs-inline,\n.visible-xs-inline-block,\n.visible-sm-block,\n.visible-sm-inline,\n.visible-sm-inline-block,\n.visible-md-block,\n.visible-md-inline,\n.visible-md-inline-block,\n.visible-lg-block,\n.visible-lg-inline,\n.visible-lg-inline-block {\n display: none !important;\n}\n@media (max-width: 767px) {\n .visible-xs {\n display: block !important;\n }\n table.visible-xs {\n display: table;\n }\n tr.visible-xs {\n display: table-row !important;\n }\n th.visible-xs,\n td.visible-xs {\n display: table-cell !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-block {\n display: block !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-inline {\n display: inline !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm {\n display: block !important;\n }\n table.visible-sm {\n display: table;\n }\n tr.visible-sm {\n display: table-row !important;\n }\n th.visible-sm,\n td.visible-sm {\n display: table-cell !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-block {\n display: block !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-inline {\n display: inline !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md {\n display: block !important;\n }\n table.visible-md {\n display: table;\n }\n tr.visible-md {\n display: table-row !important;\n }\n th.visible-md,\n td.visible-md {\n display: table-cell !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-block {\n display: block !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-inline {\n display: inline !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg {\n display: block !important;\n }\n table.visible-lg {\n display: table;\n }\n tr.visible-lg {\n display: table-row !important;\n }\n th.visible-lg,\n td.visible-lg {\n display: table-cell !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-block {\n display: block !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-inline {\n display: inline !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-inline-block {\n display: inline-block !important;\n }\n}\n@media (max-width: 767px) {\n .hidden-xs {\n display: none !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .hidden-sm {\n display: none !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .hidden-md {\n display: none !important;\n }\n}\n@media (min-width: 1200px) {\n .hidden-lg {\n display: none !important;\n }\n}\n.visible-print {\n display: none !important;\n}\n@media print {\n .visible-print {\n display: block !important;\n }\n table.visible-print {\n display: table;\n }\n tr.visible-print {\n display: table-row !important;\n }\n th.visible-print,\n td.visible-print {\n display: table-cell !important;\n }\n}\n.visible-print-block {\n display: none !important;\n}\n@media print {\n .visible-print-block {\n display: block !important;\n }\n}\n.visible-print-inline {\n display: none !important;\n}\n@media print {\n .visible-print-inline {\n display: inline !important;\n }\n}\n.visible-print-inline-block {\n display: none !important;\n}\n@media print {\n .visible-print-inline-block {\n display: inline-block !important;\n }\n}\n@media print {\n .hidden-print {\n display: none !important;\n }\n}\n/*# sourceMappingURL=bootstrap.css.map */","/*! normalize.css v3.0.2 | MIT License | git.io/normalize */\n\n//\n// 1. Set default font family to sans-serif.\n// 2. Prevent iOS text size adjust after orientation change, without disabling\n// user zoom.\n//\n\nhtml {\n font-family: sans-serif; // 1\n -ms-text-size-adjust: 100%; // 2\n -webkit-text-size-adjust: 100%; // 2\n}\n\n//\n// Remove default margin.\n//\n\nbody {\n margin: 0;\n}\n\n// HTML5 display definitions\n// ==========================================================================\n\n//\n// Correct `block` display not defined for any HTML5 element in IE 8/9.\n// Correct `block` display not defined for `details` or `summary` in IE 10/11\n// and Firefox.\n// Correct `block` display not defined for `main` in IE 11.\n//\n\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n display: block;\n}\n\n//\n// 1. Correct `inline-block` display not defined in IE 8/9.\n// 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.\n//\n\naudio,\ncanvas,\nprogress,\nvideo {\n display: inline-block; // 1\n vertical-align: baseline; // 2\n}\n\n//\n// Prevent modern browsers from displaying `audio` without controls.\n// Remove excess height in iOS 5 devices.\n//\n\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n\n//\n// Address `[hidden]` styling not present in IE 8/9/10.\n// Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.\n//\n\n[hidden],\ntemplate {\n display: none;\n}\n\n// Links\n// ==========================================================================\n\n//\n// Remove the gray background color from active links in IE 10.\n//\n\na {\n background-color: transparent;\n}\n\n//\n// Improve readability when focused and also mouse hovered in all browsers.\n//\n\na:active,\na:hover {\n outline: 0;\n}\n\n// Text-level semantics\n// ==========================================================================\n\n//\n// Address styling not present in IE 8/9/10/11, Safari, and Chrome.\n//\n\nabbr[title] {\n border-bottom: 1px dotted;\n}\n\n//\n// Address style set to `bolder` in Firefox 4+, Safari, and Chrome.\n//\n\nb,\nstrong {\n font-weight: bold;\n}\n\n//\n// Address styling not present in Safari and Chrome.\n//\n\ndfn {\n font-style: italic;\n}\n\n//\n// Address variable `h1` font-size and margin within `section` and `article`\n// contexts in Firefox 4+, Safari, and Chrome.\n//\n\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\n\n//\n// Address styling not present in IE 8/9.\n//\n\nmark {\n background: #ff0;\n color: #000;\n}\n\n//\n// Address inconsistent and variable font size in all browsers.\n//\n\nsmall {\n font-size: 80%;\n}\n\n//\n// Prevent `sub` and `sup` affecting `line-height` in all browsers.\n//\n\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\n\nsup {\n top: -0.5em;\n}\n\nsub {\n bottom: -0.25em;\n}\n\n// Embedded content\n// ==========================================================================\n\n//\n// Remove border when inside `a` element in IE 8/9/10.\n//\n\nimg {\n border: 0;\n}\n\n//\n// Correct overflow not hidden in IE 9/10/11.\n//\n\nsvg:not(:root) {\n overflow: hidden;\n}\n\n// Grouping content\n// ==========================================================================\n\n//\n// Address margin not present in IE 8/9 and Safari.\n//\n\nfigure {\n margin: 1em 40px;\n}\n\n//\n// Address differences between Firefox and other browsers.\n//\n\nhr {\n -moz-box-sizing: content-box;\n box-sizing: content-box;\n height: 0;\n}\n\n//\n// Contain overflow in all browsers.\n//\n\npre {\n overflow: auto;\n}\n\n//\n// Address odd `em`-unit font size rendering in all browsers.\n//\n\ncode,\nkbd,\npre,\nsamp {\n font-family: monospace, monospace;\n font-size: 1em;\n}\n\n// Forms\n// ==========================================================================\n\n//\n// Known limitation: by default, Chrome and Safari on OS X allow very limited\n// styling of `select`, unless a `border` property is set.\n//\n\n//\n// 1. Correct color not being inherited.\n// Known issue: affects color of disabled elements.\n// 2. Correct font properties not being inherited.\n// 3. Address margins set differently in Firefox 4+, Safari, and Chrome.\n//\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n color: inherit; // 1\n font: inherit; // 2\n margin: 0; // 3\n}\n\n//\n// Address `overflow` set to `hidden` in IE 8/9/10/11.\n//\n\nbutton {\n overflow: visible;\n}\n\n//\n// Address inconsistent `text-transform` inheritance for `button` and `select`.\n// All other form control elements do not inherit `text-transform` values.\n// Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.\n// Correct `select` style inheritance in Firefox.\n//\n\nbutton,\nselect {\n text-transform: none;\n}\n\n//\n// 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`\n// and `video` controls.\n// 2. Correct inability to style clickable `input` types in iOS.\n// 3. Improve usability and consistency of cursor style between image-type\n// `input` and others.\n//\n\nbutton,\nhtml input[type=\"button\"], // 1\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n -webkit-appearance: button; // 2\n cursor: pointer; // 3\n}\n\n//\n// Re-set default cursor for disabled elements.\n//\n\nbutton[disabled],\nhtml input[disabled] {\n cursor: default;\n}\n\n//\n// Remove inner padding and border in Firefox 4+.\n//\n\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n border: 0;\n padding: 0;\n}\n\n//\n// Address Firefox 4+ setting `line-height` on `input` using `!important` in\n// the UA stylesheet.\n//\n\ninput {\n line-height: normal;\n}\n\n//\n// It's recommended that you don't attempt to style these elements.\n// Firefox's implementation doesn't respect box-sizing, padding, or width.\n//\n// 1. Address box sizing set to `content-box` in IE 8/9/10.\n// 2. Remove excess padding in IE 8/9/10.\n//\n\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n box-sizing: border-box; // 1\n padding: 0; // 2\n}\n\n//\n// Fix the cursor style for Chrome's increment/decrement buttons. For certain\n// `font-size` values of the `input`, it causes the cursor style of the\n// decrement button to change from `default` to `text`.\n//\n\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n//\n// 1. Address `appearance` set to `searchfield` in Safari and Chrome.\n// 2. Address `box-sizing` set to `border-box` in Safari and Chrome\n// (include `-moz` to future-proof).\n//\n\ninput[type=\"search\"] {\n -webkit-appearance: textfield; // 1\n -moz-box-sizing: content-box;\n -webkit-box-sizing: content-box; // 2\n box-sizing: content-box;\n}\n\n//\n// Remove inner padding and search cancel button in Safari and Chrome on OS X.\n// Safari (but not Chrome) clips the cancel button when the search input has\n// padding (and `textfield` appearance).\n//\n\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n//\n// Define consistent border, margin, and padding.\n//\n\nfieldset {\n border: 1px solid #c0c0c0;\n margin: 0 2px;\n padding: 0.35em 0.625em 0.75em;\n}\n\n//\n// 1. Correct `color` not being inherited in IE 8/9/10/11.\n// 2. Remove padding so people aren't caught out if they zero out fieldsets.\n//\n\nlegend {\n border: 0; // 1\n padding: 0; // 2\n}\n\n//\n// Remove default vertical scrollbar in IE 8/9/10/11.\n//\n\ntextarea {\n overflow: auto;\n}\n\n//\n// Don't inherit the `font-weight` (applied by a rule above).\n// NOTE: the default cannot safely be changed in Chrome and Safari on OS X.\n//\n\noptgroup {\n font-weight: bold;\n}\n\n// Tables\n// ==========================================================================\n\n//\n// Remove most spacing between table cells.\n//\n\ntable {\n border-collapse: collapse;\n border-spacing: 0;\n}\n\ntd,\nth {\n padding: 0;\n}\n","/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n\n// ==========================================================================\n// Print styles.\n// Inlined to avoid the additional HTTP request: h5bp.com/r\n// ==========================================================================\n\n@media print {\n *,\n *:before,\n *:after {\n background: transparent !important;\n color: #000 !important; // Black prints faster: h5bp.com/s\n box-shadow: none !important;\n text-shadow: none !important;\n }\n\n a,\n a:visited {\n text-decoration: underline;\n }\n\n a[href]:after {\n content: \" (\" attr(href) \")\";\n }\n\n abbr[title]:after {\n content: \" (\" attr(title) \")\";\n }\n\n // Don't show links that are fragment identifiers,\n // or use the `javascript:` pseudo protocol\n a[href^=\"#\"]:after,\n a[href^=\"javascript:\"]:after {\n content: \"\";\n }\n\n pre,\n blockquote {\n border: 1px solid #999;\n page-break-inside: avoid;\n }\n\n thead {\n display: table-header-group; // h5bp.com/t\n }\n\n tr,\n img {\n page-break-inside: avoid;\n }\n\n img {\n max-width: 100% !important;\n }\n\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n\n h2,\n h3 {\n page-break-after: avoid;\n }\n\n // Bootstrap specific changes start\n //\n // Chrome (OSX) fix for https://github.com/twbs/bootstrap/issues/11245\n // Once fixed, we can just straight up remove this.\n select {\n background: #fff !important;\n }\n\n // Bootstrap components\n .navbar {\n display: none;\n }\n .btn,\n .dropup > .btn {\n > .caret {\n border-top-color: #000 !important;\n }\n }\n .label {\n border: 1px solid #000;\n }\n\n .table {\n border-collapse: collapse !important;\n\n td,\n th {\n background-color: #fff !important;\n }\n }\n .table-bordered {\n th,\n td {\n border: 1px solid #ddd !important;\n }\n }\n\n // Bootstrap specific changes end\n}\n","//\n// Glyphicons for Bootstrap\n//\n// Since icons are fonts, they can be placed anywhere text is placed and are\n// thus automatically sized to match the surrounding child. To use, create an\n// inline element with the appropriate classes, like so:\n//\n// <a href=\"#\"><span class=\"glyphicon glyphicon-star\"></span> Star</a>\n\n// Import the fonts\n@font-face {\n font-family: 'Glyphicons Halflings';\n src: url('@{icon-font-path}@{icon-font-name}.eot');\n src: url('@{icon-font-path}@{icon-font-name}.eot?#iefix') format('embedded-opentype'),\n url('@{icon-font-path}@{icon-font-name}.woff2') format('woff2'),\n url('@{icon-font-path}@{icon-font-name}.woff') format('woff'),\n url('@{icon-font-path}@{icon-font-name}.ttf') format('truetype'),\n url('@{icon-font-path}@{icon-font-name}.svg#@{icon-font-svg-id}') format('svg');\n}\n\n// Catchall baseclass\n.glyphicon {\n position: relative;\n top: 1px;\n display: inline-block;\n font-family: 'Glyphicons Halflings';\n font-style: normal;\n font-weight: normal;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n// Individual icons\n.glyphicon-asterisk { &:before { content: \"\\2a\"; } }\n.glyphicon-plus { &:before { content: \"\\2b\"; } }\n.glyphicon-euro,\n.glyphicon-eur { &:before { content: \"\\20ac\"; } }\n.glyphicon-minus { &:before { content: \"\\2212\"; } }\n.glyphicon-cloud { &:before { content: \"\\2601\"; } }\n.glyphicon-envelope { &:before { content: \"\\2709\"; } }\n.glyphicon-pencil { &:before { content: \"\\270f\"; } }\n.glyphicon-glass { &:before { content: \"\\e001\"; } }\n.glyphicon-music { &:before { content: \"\\e002\"; } }\n.glyphicon-search { &:before { content: \"\\e003\"; } }\n.glyphicon-heart { &:before { content: \"\\e005\"; } }\n.glyphicon-star { &:before { content: \"\\e006\"; } }\n.glyphicon-star-empty { &:before { content: \"\\e007\"; } }\n.glyphicon-user { &:before { content: \"\\e008\"; } }\n.glyphicon-film { &:before { content: \"\\e009\"; } }\n.glyphicon-th-large { &:before { content: \"\\e010\"; } }\n.glyphicon-th { &:before { content: \"\\e011\"; } }\n.glyphicon-th-list { &:before { content: \"\\e012\"; } }\n.glyphicon-ok { &:before { content: \"\\e013\"; } }\n.glyphicon-remove { &:before { content: \"\\e014\"; } }\n.glyphicon-zoom-in { &:before { content: \"\\e015\"; } }\n.glyphicon-zoom-out { &:before { content: \"\\e016\"; } }\n.glyphicon-off { &:before { content: \"\\e017\"; } }\n.glyphicon-signal { &:before { content: \"\\e018\"; } }\n.glyphicon-cog { &:before { content: \"\\e019\"; } }\n.glyphicon-trash { &:before { content: \"\\e020\"; } }\n.glyphicon-home { &:before { content: \"\\e021\"; } }\n.glyphicon-file { &:before { content: \"\\e022\"; } }\n.glyphicon-time { &:before { content: \"\\e023\"; } }\n.glyphicon-road { &:before { content: \"\\e024\"; } }\n.glyphicon-download-alt { &:before { content: \"\\e025\"; } }\n.glyphicon-download { &:before { content: \"\\e026\"; } }\n.glyphicon-upload { &:before { content: \"\\e027\"; } }\n.glyphicon-inbox { &:before { content: \"\\e028\"; } }\n.glyphicon-play-circle { &:before { content: \"\\e029\"; } }\n.glyphicon-repeat { &:before { content: \"\\e030\"; } }\n.glyphicon-refresh { &:before { content: \"\\e031\"; } }\n.glyphicon-list-alt { &:before { content: \"\\e032\"; } }\n.glyphicon-lock { &:before { content: \"\\e033\"; } }\n.glyphicon-flag { &:before { content: \"\\e034\"; } }\n.glyphicon-headphones { &:before { content: \"\\e035\"; } }\n.glyphicon-volume-off { &:before { content: \"\\e036\"; } }\n.glyphicon-volume-down { &:before { content: \"\\e037\"; } }\n.glyphicon-volume-up { &:before { content: \"\\e038\"; } }\n.glyphicon-qrcode { &:before { content: \"\\e039\"; } }\n.glyphicon-barcode { &:before { content: \"\\e040\"; } }\n.glyphicon-tag { &:before { content: \"\\e041\"; } }\n.glyphicon-tags { &:before { content: \"\\e042\"; } }\n.glyphicon-book { &:before { content: \"\\e043\"; } }\n.glyphicon-bookmark { &:before { content: \"\\e044\"; } }\n.glyphicon-print { &:before { content: \"\\e045\"; } }\n.glyphicon-camera { &:before { content: \"\\e046\"; } }\n.glyphicon-font { &:before { content: \"\\e047\"; } }\n.glyphicon-bold { &:before { content: \"\\e048\"; } }\n.glyphicon-italic { &:before { content: \"\\e049\"; } }\n.glyphicon-text-height { &:before { content: \"\\e050\"; } }\n.glyphicon-text-width { &:before { content: \"\\e051\"; } }\n.glyphicon-align-left { &:before { content: \"\\e052\"; } }\n.glyphicon-align-center { &:before { content: \"\\e053\"; } }\n.glyphicon-align-right { &:before { content: \"\\e054\"; } }\n.glyphicon-align-justify { &:before { content: \"\\e055\"; } }\n.glyphicon-list { &:before { content: \"\\e056\"; } }\n.glyphicon-indent-left { &:before { content: \"\\e057\"; } }\n.glyphicon-indent-right { &:before { content: \"\\e058\"; } }\n.glyphicon-facetime-video { &:before { content: \"\\e059\"; } }\n.glyphicon-picture { &:before { content: \"\\e060\"; } }\n.glyphicon-map-marker { &:before { content: \"\\e062\"; } }\n.glyphicon-adjust { &:before { content: \"\\e063\"; } }\n.glyphicon-tint { &:before { content: \"\\e064\"; } }\n.glyphicon-edit { &:before { content: \"\\e065\"; } }\n.glyphicon-share { &:before { content: \"\\e066\"; } }\n.glyphicon-check { &:before { content: \"\\e067\"; } }\n.glyphicon-move { &:before { content: \"\\e068\"; } }\n.glyphicon-step-backward { &:before { content: \"\\e069\"; } }\n.glyphicon-fast-backward { &:before { content: \"\\e070\"; } }\n.glyphicon-backward { &:before { content: \"\\e071\"; } }\n.glyphicon-play { &:before { content: \"\\e072\"; } }\n.glyphicon-pause { &:before { content: \"\\e073\"; } }\n.glyphicon-stop { &:before { content: \"\\e074\"; } }\n.glyphicon-forward { &:before { content: \"\\e075\"; } }\n.glyphicon-fast-forward { &:before { content: \"\\e076\"; } }\n.glyphicon-step-forward { &:before { content: \"\\e077\"; } }\n.glyphicon-eject { &:before { content: \"\\e078\"; } }\n.glyphicon-chevron-left { &:before { content: \"\\e079\"; } }\n.glyphicon-chevron-right { &:before { content: \"\\e080\"; } }\n.glyphicon-plus-sign { &:before { content: \"\\e081\"; } }\n.glyphicon-minus-sign { &:before { content: \"\\e082\"; } }\n.glyphicon-remove-sign { &:before { content: \"\\e083\"; } }\n.glyphicon-ok-sign { &:before { content: \"\\e084\"; } }\n.glyphicon-question-sign { &:before { content: \"\\e085\"; } }\n.glyphicon-info-sign { &:before { content: \"\\e086\"; } }\n.glyphicon-screenshot { &:before { content: \"\\e087\"; } }\n.glyphicon-remove-circle { &:before { content: \"\\e088\"; } }\n.glyphicon-ok-circle { &:before { content: \"\\e089\"; } }\n.glyphicon-ban-circle { &:before { content: \"\\e090\"; } }\n.glyphicon-arrow-left { &:before { content: \"\\e091\"; } }\n.glyphicon-arrow-right { &:before { content: \"\\e092\"; } }\n.glyphicon-arrow-up { &:before { content: \"\\e093\"; } }\n.glyphicon-arrow-down { &:before { content: \"\\e094\"; } }\n.glyphicon-share-alt { &:before { content: \"\\e095\"; } }\n.glyphicon-resize-full { &:before { content: \"\\e096\"; } }\n.glyphicon-resize-small { &:before { content: \"\\e097\"; } }\n.glyphicon-exclamation-sign { &:before { content: \"\\e101\"; } }\n.glyphicon-gift { &:before { content: \"\\e102\"; } }\n.glyphicon-leaf { &:before { content: \"\\e103\"; } }\n.glyphicon-fire { &:before { content: \"\\e104\"; } }\n.glyphicon-eye-open { &:before { content: \"\\e105\"; } }\n.glyphicon-eye-close { &:before { content: \"\\e106\"; } }\n.glyphicon-warning-sign { &:before { content: \"\\e107\"; } }\n.glyphicon-plane { &:before { content: \"\\e108\"; } }\n.glyphicon-calendar { &:before { content: \"\\e109\"; } }\n.glyphicon-random { &:before { content: \"\\e110\"; } }\n.glyphicon-comment { &:before { content: \"\\e111\"; } }\n.glyphicon-magnet { &:before { content: \"\\e112\"; } }\n.glyphicon-chevron-up { &:before { content: \"\\e113\"; } }\n.glyphicon-chevron-down { &:before { content: \"\\e114\"; } }\n.glyphicon-retweet { &:before { content: \"\\e115\"; } }\n.glyphicon-shopping-cart { &:before { content: \"\\e116\"; } }\n.glyphicon-folder-close { &:before { content: \"\\e117\"; } }\n.glyphicon-folder-open { &:before { content: \"\\e118\"; } }\n.glyphicon-resize-vertical { &:before { content: \"\\e119\"; } }\n.glyphicon-resize-horizontal { &:before { content: \"\\e120\"; } }\n.glyphicon-hdd { &:before { content: \"\\e121\"; } }\n.glyphicon-bullhorn { &:before { content: \"\\e122\"; } }\n.glyphicon-bell { &:before { content: \"\\e123\"; } }\n.glyphicon-certificate { &:before { content: \"\\e124\"; } }\n.glyphicon-thumbs-up { &:before { content: \"\\e125\"; } }\n.glyphicon-thumbs-down { &:before { content: \"\\e126\"; } }\n.glyphicon-hand-right { &:before { content: \"\\e127\"; } }\n.glyphicon-hand-left { &:before { content: \"\\e128\"; } }\n.glyphicon-hand-up { &:before { content: \"\\e129\"; } }\n.glyphicon-hand-down { &:before { content: \"\\e130\"; } }\n.glyphicon-circle-arrow-right { &:before { content: \"\\e131\"; } }\n.glyphicon-circle-arrow-left { &:before { content: \"\\e132\"; } }\n.glyphicon-circle-arrow-up { &:before { content: \"\\e133\"; } }\n.glyphicon-circle-arrow-down { &:before { content: \"\\e134\"; } }\n.glyphicon-globe { &:before { content: \"\\e135\"; } }\n.glyphicon-wrench { &:before { content: \"\\e136\"; } }\n.glyphicon-tasks { &:before { content: \"\\e137\"; } }\n.glyphicon-filter { &:before { content: \"\\e138\"; } }\n.glyphicon-briefcase { &:before { content: \"\\e139\"; } }\n.glyphicon-fullscreen { &:before { content: \"\\e140\"; } }\n.glyphicon-dashboard { &:before { content: \"\\e141\"; } }\n.glyphicon-paperclip { &:before { content: \"\\e142\"; } }\n.glyphicon-heart-empty { &:before { content: \"\\e143\"; } }\n.glyphicon-link { &:before { content: \"\\e144\"; } }\n.glyphicon-phone { &:before { content: \"\\e145\"; } }\n.glyphicon-pushpin { &:before { content: \"\\e146\"; } }\n.glyphicon-usd { &:before { content: \"\\e148\"; } }\n.glyphicon-gbp { &:before { content: \"\\e149\"; } }\n.glyphicon-sort { &:before { content: \"\\e150\"; } }\n.glyphicon-sort-by-alphabet { &:before { content: \"\\e151\"; } }\n.glyphicon-sort-by-alphabet-alt { &:before { content: \"\\e152\"; } }\n.glyphicon-sort-by-order { &:before { content: \"\\e153\"; } }\n.glyphicon-sort-by-order-alt { &:before { content: \"\\e154\"; } }\n.glyphicon-sort-by-attributes { &:before { content: \"\\e155\"; } }\n.glyphicon-sort-by-attributes-alt { &:before { content: \"\\e156\"; } }\n.glyphicon-unchecked { &:before { content: \"\\e157\"; } }\n.glyphicon-expand { &:before { content: \"\\e158\"; } }\n.glyphicon-collapse-down { &:before { content: \"\\e159\"; } }\n.glyphicon-collapse-up { &:before { content: \"\\e160\"; } }\n.glyphicon-log-in { &:before { content: \"\\e161\"; } }\n.glyphicon-flash { &:before { content: \"\\e162\"; } }\n.glyphicon-log-out { &:before { content: \"\\e163\"; } }\n.glyphicon-new-window { &:before { content: \"\\e164\"; } }\n.glyphicon-record { &:before { content: \"\\e165\"; } }\n.glyphicon-save { &:before { content: \"\\e166\"; } }\n.glyphicon-open { &:before { content: \"\\e167\"; } }\n.glyphicon-saved { &:before { content: \"\\e168\"; } }\n.glyphicon-import { &:before { content: \"\\e169\"; } }\n.glyphicon-export { &:before { content: \"\\e170\"; } }\n.glyphicon-send { &:before { content: \"\\e171\"; } }\n.glyphicon-floppy-disk { &:before { content: \"\\e172\"; } }\n.glyphicon-floppy-saved { &:before { content: \"\\e173\"; } }\n.glyphicon-floppy-remove { &:before { content: \"\\e174\"; } }\n.glyphicon-floppy-save { &:before { content: \"\\e175\"; } }\n.glyphicon-floppy-open { &:before { content: \"\\e176\"; } }\n.glyphicon-credit-card { &:before { content: \"\\e177\"; } }\n.glyphicon-transfer { &:before { content: \"\\e178\"; } }\n.glyphicon-cutlery { &:before { content: \"\\e179\"; } }\n.glyphicon-header { &:before { content: \"\\e180\"; } }\n.glyphicon-compressed { &:before { content: \"\\e181\"; } }\n.glyphicon-earphone { &:before { content: \"\\e182\"; } }\n.glyphicon-phone-alt { &:before { content: \"\\e183\"; } }\n.glyphicon-tower { &:before { content: \"\\e184\"; } }\n.glyphicon-stats { &:before { content: \"\\e185\"; } }\n.glyphicon-sd-video { &:before { content: \"\\e186\"; } }\n.glyphicon-hd-video { &:before { content: \"\\e187\"; } }\n.glyphicon-subtitles { &:before { content: \"\\e188\"; } }\n.glyphicon-sound-stereo { &:before { content: \"\\e189\"; } }\n.glyphicon-sound-dolby { &:before { content: \"\\e190\"; } }\n.glyphicon-sound-5-1 { &:before { content: \"\\e191\"; } }\n.glyphicon-sound-6-1 { &:before { content: \"\\e192\"; } }\n.glyphicon-sound-7-1 { &:before { content: \"\\e193\"; } }\n.glyphicon-copyright-mark { &:before { content: \"\\e194\"; } }\n.glyphicon-registration-mark { &:before { content: \"\\e195\"; } }\n.glyphicon-cloud-download { &:before { content: \"\\e197\"; } }\n.glyphicon-cloud-upload { &:before { content: \"\\e198\"; } }\n.glyphicon-tree-conifer { &:before { content: \"\\e199\"; } }\n.glyphicon-tree-deciduous { &:before { content: \"\\e200\"; } }\n.glyphicon-cd { &:before { content: \"\\e201\"; } }\n.glyphicon-save-file { &:before { content: \"\\e202\"; } }\n.glyphicon-open-file { &:before { content: \"\\e203\"; } }\n.glyphicon-level-up { &:before { content: \"\\e204\"; } }\n.glyphicon-copy { &:before { content: \"\\e205\"; } }\n.glyphicon-paste { &:before { content: \"\\e206\"; } }\n// The following 2 Glyphicons are omitted for the time being because\n// they currently use Unicode codepoints that are outside the\n// Basic Multilingual Plane (BMP). Older buggy versions of WebKit can't handle\n// non-BMP codepoints in CSS string escapes, and thus can't display these two icons.\n// Notably, the bug affects some older versions of the Android Browser.\n// More info: https://github.com/twbs/bootstrap/issues/10106\n// .glyphicon-door { &:before { content: \"\\1f6aa\"; } }\n// .glyphicon-key { &:before { content: \"\\1f511\"; } }\n.glyphicon-alert { &:before { content: \"\\e209\"; } }\n.glyphicon-equalizer { &:before { content: \"\\e210\"; } }\n.glyphicon-king { &:before { content: \"\\e211\"; } }\n.glyphicon-queen { &:before { content: \"\\e212\"; } }\n.glyphicon-pawn { &:before { content: \"\\e213\"; } }\n.glyphicon-bishop { &:before { content: \"\\e214\"; } }\n.glyphicon-knight { &:before { content: \"\\e215\"; } }\n.glyphicon-baby-formula { &:before { content: \"\\e216\"; } }\n.glyphicon-tent { &:before { content: \"\\26fa\"; } }\n.glyphicon-blackboard { &:before { content: \"\\e218\"; } }\n.glyphicon-bed { &:before { content: \"\\e219\"; } }\n.glyphicon-apple { &:before { content: \"\\f8ff\"; } }\n.glyphicon-erase { &:before { content: \"\\e221\"; } }\n.glyphicon-hourglass { &:before { content: \"\\231b\"; } }\n.glyphicon-lamp { &:before { content: \"\\e223\"; } }\n.glyphicon-duplicate { &:before { content: \"\\e224\"; } }\n.glyphicon-piggy-bank { &:before { content: \"\\e225\"; } }\n.glyphicon-scissors { &:before { content: \"\\e226\"; } }\n.glyphicon-bitcoin { &:before { content: \"\\e227\"; } }\n.glyphicon-btc { &:before { content: \"\\e227\"; } }\n.glyphicon-xbt { &:before { content: \"\\e227\"; } }\n.glyphicon-yen { &:before { content: \"\\00a5\"; } }\n.glyphicon-jpy { &:before { content: \"\\00a5\"; } }\n.glyphicon-ruble { &:before { content: \"\\20bd\"; } }\n.glyphicon-rub { &:before { content: \"\\20bd\"; } }\n.glyphicon-scale { &:before { content: \"\\e230\"; } }\n.glyphicon-ice-lolly { &:before { content: \"\\e231\"; } }\n.glyphicon-ice-lolly-tasted { &:before { content: \"\\e232\"; } }\n.glyphicon-education { &:before { content: \"\\e233\"; } }\n.glyphicon-option-horizontal { &:before { content: \"\\e234\"; } }\n.glyphicon-option-vertical { &:before { content: \"\\e235\"; } }\n.glyphicon-menu-hamburger { &:before { content: \"\\e236\"; } }\n.glyphicon-modal-window { &:before { content: \"\\e237\"; } }\n.glyphicon-oil { &:before { content: \"\\e238\"; } }\n.glyphicon-grain { &:before { content: \"\\e239\"; } }\n.glyphicon-sunglasses { &:before { content: \"\\e240\"; } }\n.glyphicon-text-size { &:before { content: \"\\e241\"; } }\n.glyphicon-text-color { &:before { content: \"\\e242\"; } }\n.glyphicon-text-background { &:before { content: \"\\e243\"; } }\n.glyphicon-object-align-top { &:before { content: \"\\e244\"; } }\n.glyphicon-object-align-bottom { &:before { content: \"\\e245\"; } }\n.glyphicon-object-align-horizontal{ &:before { content: \"\\e246\"; } }\n.glyphicon-object-align-left { &:before { content: \"\\e247\"; } }\n.glyphicon-object-align-vertical { &:before { content: \"\\e248\"; } }\n.glyphicon-object-align-right { &:before { content: \"\\e249\"; } }\n.glyphicon-triangle-right { &:before { content: \"\\e250\"; } }\n.glyphicon-triangle-left { &:before { content: \"\\e251\"; } }\n.glyphicon-triangle-bottom { &:before { content: \"\\e252\"; } }\n.glyphicon-triangle-top { &:before { content: \"\\e253\"; } }\n.glyphicon-console { &:before { content: \"\\e254\"; } }\n.glyphicon-superscript { &:before { content: \"\\e255\"; } }\n.glyphicon-subscript { &:before { content: \"\\e256\"; } }\n.glyphicon-menu-left { &:before { content: \"\\e257\"; } }\n.glyphicon-menu-right { &:before { content: \"\\e258\"; } }\n.glyphicon-menu-down { &:before { content: \"\\e259\"; } }\n.glyphicon-menu-up { &:before { content: \"\\e260\"; } }\n","//\n// Scaffolding\n// --------------------------------------------------\n\n\n// Reset the box-sizing\n//\n// Heads up! This reset may cause conflicts with some third-party widgets.\n// For recommendations on resolving such conflicts, see\n// http://getbootstrap.com/getting-started/#third-box-sizing\n* {\n .box-sizing(border-box);\n}\n*:before,\n*:after {\n .box-sizing(border-box);\n}\n\n\n// Body reset\n\nhtml {\n font-size: 10px;\n -webkit-tap-highlight-color: rgba(0,0,0,0);\n}\n\nbody {\n font-family: @font-family-base;\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @text-color;\n background-color: @body-bg;\n}\n\n// Reset fonts for relevant elements\ninput,\nbutton,\nselect,\ntextarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\n\n// Links\n\na {\n color: @link-color;\n text-decoration: none;\n\n &:hover,\n &:focus {\n color: @link-hover-color;\n text-decoration: @link-hover-decoration;\n }\n\n &:focus {\n .tab-focus();\n }\n}\n\n\n// Figures\n//\n// We reset this here because previously Normalize had no `figure` margins. This\n// ensures we don't break anyone's use of the element.\n\nfigure {\n margin: 0;\n}\n\n\n// Images\n\nimg {\n vertical-align: middle;\n}\n\n// Responsive images (ensure images don't scale beyond their parents)\n.img-responsive {\n .img-responsive();\n}\n\n// Rounded corners\n.img-rounded {\n border-radius: @border-radius-large;\n}\n\n// Image thumbnails\n//\n// Heads up! This is mixin-ed into thumbnails.less for `.thumbnail`.\n.img-thumbnail {\n padding: @thumbnail-padding;\n line-height: @line-height-base;\n background-color: @thumbnail-bg;\n border: 1px solid @thumbnail-border;\n border-radius: @thumbnail-border-radius;\n .transition(all .2s ease-in-out);\n\n // Keep them at most 100% wide\n .img-responsive(inline-block);\n}\n\n// Perfect circle\n.img-circle {\n border-radius: 50%; // set radius in percents\n}\n\n\n// Horizontal rules\n\nhr {\n margin-top: @line-height-computed;\n margin-bottom: @line-height-computed;\n border: 0;\n border-top: 1px solid @hr-border;\n}\n\n\n// Only display content to screen readers\n//\n// See: http://a11yproject.com/posts/how-to-hide-content/\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0,0,0,0);\n border: 0;\n}\n\n// Use in conjunction with .sr-only to only display content when it's focused.\n// Useful for \"Skip to main content\" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1\n// Credit: HTML5 Boilerplate\n\n.sr-only-focusable {\n &:active,\n &:focus {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n overflow: visible;\n clip: auto;\n }\n}\n\n\n// iOS \"clickable elements\" fix for role=\"button\"\n//\n// Fixes \"clickability\" issue (and more generally, the firing of events such as focus as well)\n// for traditionally non-focusable elements with role=\"button\"\n// see https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile\n// Upstream patch for normalize.css submitted: https://github.com/necolas/normalize.css/pull/379 - remove this fix once that is merged\n\n[role=\"button\"] {\n cursor: pointer;\n}","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They will be removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility){\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n","// WebKit-style focus\n\n.tab-focus() {\n // Default\n outline: thin dotted;\n // WebKit\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n","// Image Mixins\n// - Responsive image\n// - Retina image\n\n\n// Responsive image\n//\n// Keep images from scaling beyond the width of their parents.\n.img-responsive(@display: block) {\n display: @display;\n max-width: 100%; // Part 1: Set a maximum relative to the parent\n height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching\n}\n\n\n// Retina image\n//\n// Short retina mixin for setting background-image and -size. Note that the\n// spelling of `min--moz-device-pixel-ratio` is intentional.\n.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) {\n background-image: url(\"@{file-1x}\");\n\n @media\n only screen and (-webkit-min-device-pixel-ratio: 2),\n only screen and ( min--moz-device-pixel-ratio: 2),\n only screen and ( -o-min-device-pixel-ratio: 2/1),\n only screen and ( min-device-pixel-ratio: 2),\n only screen and ( min-resolution: 192dpi),\n only screen and ( min-resolution: 2dppx) {\n background-image: url(\"@{file-2x}\");\n background-size: @width-1x @height-1x;\n }\n}\n","//\n// Typography\n// --------------------------------------------------\n\n\n// Headings\n// -------------------------\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n font-family: @headings-font-family;\n font-weight: @headings-font-weight;\n line-height: @headings-line-height;\n color: @headings-color;\n\n small,\n .small {\n font-weight: normal;\n line-height: 1;\n color: @headings-small-color;\n }\n}\n\nh1, .h1,\nh2, .h2,\nh3, .h3 {\n margin-top: @line-height-computed;\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 65%;\n }\n}\nh4, .h4,\nh5, .h5,\nh6, .h6 {\n margin-top: (@line-height-computed / 2);\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 75%;\n }\n}\n\nh1, .h1 { font-size: @font-size-h1; }\nh2, .h2 { font-size: @font-size-h2; }\nh3, .h3 { font-size: @font-size-h3; }\nh4, .h4 { font-size: @font-size-h4; }\nh5, .h5 { font-size: @font-size-h5; }\nh6, .h6 { font-size: @font-size-h6; }\n\n\n// Body text\n// -------------------------\n\np {\n margin: 0 0 (@line-height-computed / 2);\n}\n\n.lead {\n margin-bottom: @line-height-computed;\n font-size: floor((@font-size-base * 1.15));\n font-weight: 300;\n line-height: 1.4;\n\n @media (min-width: @screen-sm-min) {\n font-size: (@font-size-base * 1.5);\n }\n}\n\n\n// Emphasis & misc\n// -------------------------\n\n// Ex: (12px small font / 14px base font) * 100% = about 85%\nsmall,\n.small {\n font-size: floor((100% * @font-size-small / @font-size-base));\n}\n\nmark,\n.mark {\n background-color: @state-warning-bg;\n padding: .2em;\n}\n\n// Alignment\n.text-left { text-align: left; }\n.text-right { text-align: right; }\n.text-center { text-align: center; }\n.text-justify { text-align: justify; }\n.text-nowrap { white-space: nowrap; }\n\n// Transformation\n.text-lowercase { text-transform: lowercase; }\n.text-uppercase { text-transform: uppercase; }\n.text-capitalize { text-transform: capitalize; }\n\n// Contextual colors\n.text-muted {\n color: @text-muted;\n}\n.text-primary {\n .text-emphasis-variant(@brand-primary);\n}\n.text-success {\n .text-emphasis-variant(@state-success-text);\n}\n.text-info {\n .text-emphasis-variant(@state-info-text);\n}\n.text-warning {\n .text-emphasis-variant(@state-warning-text);\n}\n.text-danger {\n .text-emphasis-variant(@state-danger-text);\n}\n\n// Contextual backgrounds\n// For now we'll leave these alongside the text classes until v4 when we can\n// safely shift things around (per SemVer rules).\n.bg-primary {\n // Given the contrast here, this is the only class to have its color inverted\n // automatically.\n color: #fff;\n .bg-variant(@brand-primary);\n}\n.bg-success {\n .bg-variant(@state-success-bg);\n}\n.bg-info {\n .bg-variant(@state-info-bg);\n}\n.bg-warning {\n .bg-variant(@state-warning-bg);\n}\n.bg-danger {\n .bg-variant(@state-danger-bg);\n}\n\n\n// Page header\n// -------------------------\n\n.page-header {\n padding-bottom: ((@line-height-computed / 2) - 1);\n margin: (@line-height-computed * 2) 0 @line-height-computed;\n border-bottom: 1px solid @page-header-border-color;\n}\n\n\n// Lists\n// -------------------------\n\n// Unordered and Ordered lists\nul,\nol {\n margin-top: 0;\n margin-bottom: (@line-height-computed / 2);\n ul,\n ol {\n margin-bottom: 0;\n }\n}\n\n// List options\n\n// Unstyled keeps list items block level, just removes default browser padding and list-style\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n\n// Inline turns list items into inline-block\n.list-inline {\n .list-unstyled();\n margin-left: -5px;\n\n > li {\n display: inline-block;\n padding-left: 5px;\n padding-right: 5px;\n }\n}\n\n// Description Lists\ndl {\n margin-top: 0; // Remove browser default\n margin-bottom: @line-height-computed;\n}\ndt,\ndd {\n line-height: @line-height-base;\n}\ndt {\n font-weight: bold;\n}\ndd {\n margin-left: 0; // Undo browser default\n}\n\n// Horizontal description lists\n//\n// Defaults to being stacked without any of the below styles applied, until the\n// grid breakpoint is reached (default of ~768px).\n\n.dl-horizontal {\n dd {\n &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present\n }\n\n @media (min-width: @grid-float-breakpoint) {\n dt {\n float: left;\n width: (@dl-horizontal-offset - 20);\n clear: left;\n text-align: right;\n .text-overflow();\n }\n dd {\n margin-left: @dl-horizontal-offset;\n }\n }\n}\n\n\n// Misc\n// -------------------------\n\n// Abbreviations and acronyms\nabbr[title],\n// Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257\nabbr[data-original-title] {\n cursor: help;\n border-bottom: 1px dotted @abbr-border-color;\n}\n.initialism {\n font-size: 90%;\n .text-uppercase();\n}\n\n// Blockquotes\nblockquote {\n padding: (@line-height-computed / 2) @line-height-computed;\n margin: 0 0 @line-height-computed;\n font-size: @blockquote-font-size;\n border-left: 5px solid @blockquote-border-color;\n\n p,\n ul,\n ol {\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n // Note: Deprecated small and .small as of v3.1.0\n // Context: https://github.com/twbs/bootstrap/issues/11660\n footer,\n small,\n .small {\n display: block;\n font-size: 80%; // back to default font-size\n line-height: @line-height-base;\n color: @blockquote-small-color;\n\n &:before {\n content: '\\2014 \\00A0'; // em dash, nbsp\n }\n }\n}\n\n// Opposite alignment of blockquote\n//\n// Heads up: `blockquote.pull-right` has been deprecated as of v3.1.0.\n.blockquote-reverse,\nblockquote.pull-right {\n padding-right: 15px;\n padding-left: 0;\n border-right: 5px solid @blockquote-border-color;\n border-left: 0;\n text-align: right;\n\n // Account for citation\n footer,\n small,\n .small {\n &:before { content: ''; }\n &:after {\n content: '\\00A0 \\2014'; // nbsp, em dash\n }\n }\n}\n\n// Addresses\naddress {\n margin-bottom: @line-height-computed;\n font-style: normal;\n line-height: @line-height-base;\n}\n","// Typography\n\n.text-emphasis-variant(@color) {\n color: @color;\n a&:hover {\n color: darken(@color, 10%);\n }\n}\n","// Contextual backgrounds\n\n.bg-variant(@color) {\n background-color: @color;\n a&:hover {\n background-color: darken(@color, 10%);\n }\n}\n","// Text overflow\n// Requires inline-block or block for proper styling\n\n.text-overflow() {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n","//\n// Code (inline and block)\n// --------------------------------------------------\n\n\n// Inline and block code styles\ncode,\nkbd,\npre,\nsamp {\n font-family: @font-family-monospace;\n}\n\n// Inline code\ncode {\n padding: 2px 4px;\n font-size: 90%;\n color: @code-color;\n background-color: @code-bg;\n border-radius: @border-radius-base;\n}\n\n// User input typically entered via keyboard\nkbd {\n padding: 2px 4px;\n font-size: 90%;\n color: @kbd-color;\n background-color: @kbd-bg;\n border-radius: @border-radius-small;\n box-shadow: inset 0 -1px 0 rgba(0,0,0,.25);\n\n kbd {\n padding: 0;\n font-size: 100%;\n font-weight: bold;\n box-shadow: none;\n }\n}\n\n// Blocks of code\npre {\n display: block;\n padding: ((@line-height-computed - 1) / 2);\n margin: 0 0 (@line-height-computed / 2);\n font-size: (@font-size-base - 1); // 14px to 13px\n line-height: @line-height-base;\n word-break: break-all;\n word-wrap: break-word;\n color: @pre-color;\n background-color: @pre-bg;\n border: 1px solid @pre-border-color;\n border-radius: @border-radius-base;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre-wrap;\n background-color: transparent;\n border-radius: 0;\n }\n}\n\n// Enable scrollable blocks of code\n.pre-scrollable {\n max-height: @pre-scrollable-max-height;\n overflow-y: scroll;\n}\n","//\n// Grid system\n// --------------------------------------------------\n\n\n// Container widths\n//\n// Set the container width, and override it for fixed navbars in media queries.\n\n.container {\n .container-fixed();\n\n @media (min-width: @screen-sm-min) {\n width: @container-sm;\n }\n @media (min-width: @screen-md-min) {\n width: @container-md;\n }\n @media (min-width: @screen-lg-min) {\n width: @container-lg;\n }\n}\n\n\n// Fluid container\n//\n// Utilizes the mixin meant for fixed width containers, but without any defined\n// width for fluid, full width layouts.\n\n.container-fluid {\n .container-fixed();\n}\n\n\n// Row\n//\n// Rows contain and clear the floats of your columns.\n\n.row {\n .make-row();\n}\n\n\n// Columns\n//\n// Common styles for small and large grid columns\n\n.make-grid-columns();\n\n\n// Extra small grid\n//\n// Columns, offsets, pushes, and pulls for extra small devices like\n// smartphones.\n\n.make-grid(xs);\n\n\n// Small grid\n//\n// Columns, offsets, pushes, and pulls for the small device range, from phones\n// to tablets.\n\n@media (min-width: @screen-sm-min) {\n .make-grid(sm);\n}\n\n\n// Medium grid\n//\n// Columns, offsets, pushes, and pulls for the desktop device range.\n\n@media (min-width: @screen-md-min) {\n .make-grid(md);\n}\n\n\n// Large grid\n//\n// Columns, offsets, pushes, and pulls for the large desktop device range.\n\n@media (min-width: @screen-lg-min) {\n .make-grid(lg);\n}\n","// Grid system\n//\n// Generate semantic grid columns with these mixins.\n\n// Centered container element\n.container-fixed(@gutter: @grid-gutter-width) {\n margin-right: auto;\n margin-left: auto;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n &:extend(.clearfix all);\n}\n\n// Creates a wrapper for a series of columns\n.make-row(@gutter: @grid-gutter-width) {\n margin-left: (@gutter / -2);\n margin-right: (@gutter / -2);\n &:extend(.clearfix all);\n}\n\n// Generate the extra small columns\n.make-xs-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n float: left;\n width: percentage((@columns / @grid-columns));\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n}\n.make-xs-column-offset(@columns) {\n margin-left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-push(@columns) {\n left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-pull(@columns) {\n right: percentage((@columns / @grid-columns));\n}\n\n// Generate the small columns\n.make-sm-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-sm-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-offset(@columns) {\n @media (min-width: @screen-sm-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-push(@columns) {\n @media (min-width: @screen-sm-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-pull(@columns) {\n @media (min-width: @screen-sm-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the medium columns\n.make-md-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-md-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-offset(@columns) {\n @media (min-width: @screen-md-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-push(@columns) {\n @media (min-width: @screen-md-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-pull(@columns) {\n @media (min-width: @screen-md-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the large columns\n.make-lg-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-lg-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-offset(@columns) {\n @media (min-width: @screen-lg-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-push(@columns) {\n @media (min-width: @screen-lg-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-pull(@columns) {\n @media (min-width: @screen-lg-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n","// Framework grid generation\n//\n// Used only by Bootstrap to generate the correct number of grid classes given\n// any value of `@grid-columns`.\n\n.make-grid-columns() {\n // Common styles for all sizes of grid columns, widths 1-12\n .col(@index) { // initial\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general; \"=<\" isn't a typo\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n position: relative;\n // Prevent columns from collapsing when empty\n min-height: 1px;\n // Inner gutter via padding\n padding-left: (@grid-gutter-width / 2);\n padding-right: (@grid-gutter-width / 2);\n }\n }\n .col(1); // kickstart it\n}\n\n.float-grid-columns(@class) {\n .col(@index) { // initial\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n float: left;\n }\n }\n .col(1); // kickstart it\n}\n\n.calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) {\n .col-@{class}-@{index} {\n width: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) and (@index > 0) {\n .col-@{class}-push-@{index} {\n left: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) and (@index = 0) {\n .col-@{class}-push-0 {\n left: auto;\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index > 0) {\n .col-@{class}-pull-@{index} {\n right: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index = 0) {\n .col-@{class}-pull-0 {\n right: auto;\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = offset) {\n .col-@{class}-offset-@{index} {\n margin-left: percentage((@index / @grid-columns));\n }\n}\n\n// Basic looping in LESS\n.loop-grid-columns(@index, @class, @type) when (@index >= 0) {\n .calc-grid-column(@index, @class, @type);\n // next iteration\n .loop-grid-columns((@index - 1), @class, @type);\n}\n\n// Create grid for specific class\n.make-grid(@class) {\n .float-grid-columns(@class);\n .loop-grid-columns(@grid-columns, @class, width);\n .loop-grid-columns(@grid-columns, @class, pull);\n .loop-grid-columns(@grid-columns, @class, push);\n .loop-grid-columns(@grid-columns, @class, offset);\n}\n","//\n// Tables\n// --------------------------------------------------\n\n\ntable {\n background-color: @table-bg;\n}\ncaption {\n padding-top: @table-cell-padding;\n padding-bottom: @table-cell-padding;\n color: @text-muted;\n text-align: left;\n}\nth {\n text-align: left;\n}\n\n\n// Baseline styles\n\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: @line-height-computed;\n // Cells\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n padding: @table-cell-padding;\n line-height: @line-height-base;\n vertical-align: top;\n border-top: 1px solid @table-border-color;\n }\n }\n }\n // Bottom align for column headings\n > thead > tr > th {\n vertical-align: bottom;\n border-bottom: 2px solid @table-border-color;\n }\n // Remove top border from thead by default\n > caption + thead,\n > colgroup + thead,\n > thead:first-child {\n > tr:first-child {\n > th,\n > td {\n border-top: 0;\n }\n }\n }\n // Account for multiple tbody instances\n > tbody + tbody {\n border-top: 2px solid @table-border-color;\n }\n\n // Nesting\n .table {\n background-color: @body-bg;\n }\n}\n\n\n// Condensed table w/ half padding\n\n.table-condensed {\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n padding: @table-condensed-cell-padding;\n }\n }\n }\n}\n\n\n// Bordered version\n//\n// Add borders all around the table and between all the columns.\n\n.table-bordered {\n border: 1px solid @table-border-color;\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n border: 1px solid @table-border-color;\n }\n }\n }\n > thead > tr {\n > th,\n > td {\n border-bottom-width: 2px;\n }\n }\n}\n\n\n// Zebra-striping\n//\n// Default zebra-stripe styles (alternating gray and transparent backgrounds)\n\n.table-striped {\n > tbody > tr:nth-of-type(odd) {\n background-color: @table-bg-accent;\n }\n}\n\n\n// Hover effect\n//\n// Placed here since it has to come after the potential zebra striping\n\n.table-hover {\n > tbody > tr:hover {\n background-color: @table-bg-hover;\n }\n}\n\n\n// Table cell sizing\n//\n// Reset default table behavior\n\ntable col[class*=\"col-\"] {\n position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623)\n float: none;\n display: table-column;\n}\ntable {\n td,\n th {\n &[class*=\"col-\"] {\n position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623)\n float: none;\n display: table-cell;\n }\n }\n}\n\n\n// Table backgrounds\n//\n// Exact selectors below required to override `.table-striped` and prevent\n// inheritance to nested tables.\n\n// Generate the contextual variants\n.table-row-variant(active; @table-bg-active);\n.table-row-variant(success; @state-success-bg);\n.table-row-variant(info; @state-info-bg);\n.table-row-variant(warning; @state-warning-bg);\n.table-row-variant(danger; @state-danger-bg);\n\n\n// Responsive tables\n//\n// Wrap your tables in `.table-responsive` and we'll make them mobile friendly\n// by enabling horizontal scrolling. Only applies <768px. Everything above that\n// will display normally.\n\n.table-responsive {\n overflow-x: auto;\n min-height: 0.01%; // Workaround for IE9 bug (see https://github.com/twbs/bootstrap/issues/14837)\n\n @media screen and (max-width: @screen-xs-max) {\n width: 100%;\n margin-bottom: (@line-height-computed * 0.75);\n overflow-y: hidden;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n border: 1px solid @table-border-color;\n\n // Tighten up spacing\n > .table {\n margin-bottom: 0;\n\n // Ensure the content doesn't wrap\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n white-space: nowrap;\n }\n }\n }\n }\n\n // Special overrides for the bordered tables\n > .table-bordered {\n border: 0;\n\n // Nuke the appropriate borders so that the parent can handle them\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th:first-child,\n > td:first-child {\n border-left: 0;\n }\n > th:last-child,\n > td:last-child {\n border-right: 0;\n }\n }\n }\n\n // Only nuke the last row's bottom-border in `tbody` and `tfoot` since\n // chances are there will be only one `tr` in a `thead` and that would\n // remove the border altogether.\n > tbody,\n > tfoot {\n > tr:last-child {\n > th,\n > td {\n border-bottom: 0;\n }\n }\n }\n\n }\n }\n}\n","// Tables\n\n.table-row-variant(@state; @background) {\n // Exact selectors below required to override `.table-striped` and prevent\n // inheritance to nested tables.\n .table > thead > tr,\n .table > tbody > tr,\n .table > tfoot > tr {\n > td.@{state},\n > th.@{state},\n &.@{state} > td,\n &.@{state} > th {\n background-color: @background;\n }\n }\n\n // Hover states for `.table-hover`\n // Note: this is not available for cells or rows within `thead` or `tfoot`.\n .table-hover > tbody > tr {\n > td.@{state}:hover,\n > th.@{state}:hover,\n &.@{state}:hover > td,\n &:hover > .@{state},\n &.@{state}:hover > th {\n background-color: darken(@background, 5%);\n }\n }\n}\n","//\n// Forms\n// --------------------------------------------------\n\n\n// Normalize non-controls\n//\n// Restyle and baseline non-control form elements.\n\nfieldset {\n padding: 0;\n margin: 0;\n border: 0;\n // Chrome and Firefox set a `min-width: min-content;` on fieldsets,\n // so we reset that to ensure it behaves more like a standard block element.\n // See https://github.com/twbs/bootstrap/issues/12359.\n min-width: 0;\n}\n\nlegend {\n display: block;\n width: 100%;\n padding: 0;\n margin-bottom: @line-height-computed;\n font-size: (@font-size-base * 1.5);\n line-height: inherit;\n color: @legend-color;\n border: 0;\n border-bottom: 1px solid @legend-border-color;\n}\n\nlabel {\n display: inline-block;\n max-width: 100%; // Force IE8 to wrap long content (see https://github.com/twbs/bootstrap/issues/13141)\n margin-bottom: 5px;\n font-weight: bold;\n}\n\n\n// Normalize form controls\n//\n// While most of our form styles require extra classes, some basic normalization\n// is required to ensure optimum display with or without those classes to better\n// address browser inconsistencies.\n\n// Override content-box in Normalize (* isn't specific enough)\ninput[type=\"search\"] {\n .box-sizing(border-box);\n}\n\n// Position radios and checkboxes better\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n margin: 4px 0 0;\n margin-top: 1px \\9; // IE8-9\n line-height: normal;\n}\n\n// Set the height of file controls to match text inputs\ninput[type=\"file\"] {\n display: block;\n}\n\n// Make range inputs behave like textual form controls\ninput[type=\"range\"] {\n display: block;\n width: 100%;\n}\n\n// Make multiple select elements height not fixed\nselect[multiple],\nselect[size] {\n height: auto;\n}\n\n// Focus for file, radio, and checkbox\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n .tab-focus();\n}\n\n// Adjust output element\noutput {\n display: block;\n padding-top: (@padding-base-vertical + 1);\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @input-color;\n}\n\n\n// Common form controls\n//\n// Shared size and type resets for form controls. Apply `.form-control` to any\n// of the following form controls:\n//\n// select\n// textarea\n// input[type=\"text\"]\n// input[type=\"password\"]\n// input[type=\"datetime\"]\n// input[type=\"datetime-local\"]\n// input[type=\"date\"]\n// input[type=\"month\"]\n// input[type=\"time\"]\n// input[type=\"week\"]\n// input[type=\"number\"]\n// input[type=\"email\"]\n// input[type=\"url\"]\n// input[type=\"search\"]\n// input[type=\"tel\"]\n// input[type=\"color\"]\n\n.form-control {\n display: block;\n width: 100%;\n height: @input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border)\n padding: @padding-base-vertical @padding-base-horizontal;\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @input-color;\n background-color: @input-bg;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid @input-border;\n border-radius: @input-border-radius; // Note: This has no effect on <select>s in some browsers, due to the limited stylability of <select>s in CSS.\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.075));\n .transition(~\"border-color ease-in-out .15s, box-shadow ease-in-out .15s\");\n\n // Customize the `:focus` state to imitate native WebKit styles.\n .form-control-focus();\n\n // Placeholder\n .placeholder();\n\n // Disabled and read-only inputs\n //\n // HTML5 says that controls under a fieldset > legend:first-child won't be\n // disabled if the fieldset is disabled. Due to implementation difficulty, we\n // don't honor that edge case; we style them as disabled anyway.\n &[disabled],\n &[readonly],\n fieldset[disabled] & {\n background-color: @input-bg-disabled;\n opacity: 1; // iOS fix for unreadable disabled content; see https://github.com/twbs/bootstrap/issues/11655\n }\n\n &[disabled],\n fieldset[disabled] & {\n cursor: @cursor-disabled;\n }\n\n // Reset height for `textarea`s\n textarea& {\n height: auto;\n }\n}\n\n\n// Search inputs in iOS\n//\n// This overrides the extra rounded corners on search inputs in iOS so that our\n// `.form-control` class can properly style them. Note that this cannot simply\n// be added to `.form-control` as it's not specific enough. For details, see\n// https://github.com/twbs/bootstrap/issues/11586.\n\ninput[type=\"search\"] {\n -webkit-appearance: none;\n}\n\n\n// Special styles for iOS temporal inputs\n//\n// In Mobile Safari, setting `display: block` on temporal inputs causes the\n// text within the input to become vertically misaligned. As a workaround, we\n// set a pixel line-height that matches the given height of the input, but only\n// for Safari. See https://bugs.webkit.org/show_bug.cgi?id=139848\n\n@media screen and (-webkit-min-device-pixel-ratio: 0) {\n input[type=\"date\"],\n input[type=\"time\"],\n input[type=\"datetime-local\"],\n input[type=\"month\"] {\n line-height: @input-height-base;\n\n &.input-sm,\n .input-group-sm & {\n line-height: @input-height-small;\n }\n\n &.input-lg,\n .input-group-lg & {\n line-height: @input-height-large;\n }\n }\n}\n\n\n// Form groups\n//\n// Designed to help with the organization and spacing of vertical forms. For\n// horizontal forms, use the predefined grid classes.\n\n.form-group {\n margin-bottom: @form-group-margin-bottom;\n}\n\n\n// Checkboxes and radios\n//\n// Indent the labels to position radios/checkboxes as hanging controls.\n\n.radio,\n.checkbox {\n position: relative;\n display: block;\n margin-top: 10px;\n margin-bottom: 10px;\n\n label {\n min-height: @line-height-computed; // Ensure the input doesn't jump when there is no text\n padding-left: 20px;\n margin-bottom: 0;\n font-weight: normal;\n cursor: pointer;\n }\n}\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n position: absolute;\n margin-left: -20px;\n margin-top: 4px \\9;\n}\n\n.radio + .radio,\n.checkbox + .checkbox {\n margin-top: -5px; // Move up sibling radios or checkboxes for tighter spacing\n}\n\n// Radios and checkboxes on same line\n.radio-inline,\n.checkbox-inline {\n position: relative;\n display: inline-block;\n padding-left: 20px;\n margin-bottom: 0;\n vertical-align: middle;\n font-weight: normal;\n cursor: pointer;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n margin-top: 0;\n margin-left: 10px; // space out consecutive inline controls\n}\n\n// Apply same disabled cursor tweak as for inputs\n// Some special care is needed because <label>s don't inherit their parent's `cursor`.\n//\n// Note: Neither radios nor checkboxes can be readonly.\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n &[disabled],\n &.disabled,\n fieldset[disabled] & {\n cursor: @cursor-disabled;\n }\n}\n// These classes are used directly on <label>s\n.radio-inline,\n.checkbox-inline {\n &.disabled,\n fieldset[disabled] & {\n cursor: @cursor-disabled;\n }\n}\n// These classes are used on elements with <label> descendants\n.radio,\n.checkbox {\n &.disabled,\n fieldset[disabled] & {\n label {\n cursor: @cursor-disabled;\n }\n }\n}\n\n\n// Static form control text\n//\n// Apply class to a `p` element to make any string of text align with labels in\n// a horizontal form layout.\n\n.form-control-static {\n // Size it appropriately next to real form controls\n padding-top: (@padding-base-vertical + 1);\n padding-bottom: (@padding-base-vertical + 1);\n // Remove default margin from `p`\n margin-bottom: 0;\n min-height: (@line-height-computed + @font-size-base);\n\n &.input-lg,\n &.input-sm {\n padding-left: 0;\n padding-right: 0;\n }\n}\n\n\n// Form control sizing\n//\n// Build on `.form-control` with modifier classes to decrease or increase the\n// height and font-size of form controls.\n//\n// The `.form-group-* form-control` variations are sadly duplicated to avoid the\n// issue documented in https://github.com/twbs/bootstrap/issues/15074.\n\n.input-sm {\n .input-size(@input-height-small; @padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @input-border-radius-small);\n}\n.form-group-sm {\n .form-control {\n .input-size(@input-height-small; @padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @input-border-radius-small);\n }\n .form-control-static {\n height: @input-height-small;\n padding: @padding-small-vertical @padding-small-horizontal;\n font-size: @font-size-small;\n line-height: @line-height-small;\n min-height: (@line-height-computed + @font-size-small);\n }\n}\n\n.input-lg {\n .input-size(@input-height-large; @padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @input-border-radius-large);\n}\n.form-group-lg {\n .form-control {\n .input-size(@input-height-large; @padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @input-border-radius-large);\n }\n .form-control-static {\n height: @input-height-large;\n padding: @padding-large-vertical @padding-large-horizontal;\n font-size: @font-size-large;\n line-height: @line-height-large;\n min-height: (@line-height-computed + @font-size-large);\n }\n}\n\n\n// Form control feedback states\n//\n// Apply contextual and semantic states to individual form controls.\n\n.has-feedback {\n // Enable absolute positioning\n position: relative;\n\n // Ensure icons don't overlap text\n .form-control {\n padding-right: (@input-height-base * 1.25);\n }\n}\n// Feedback icon (requires .glyphicon classes)\n.form-control-feedback {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2; // Ensure icon is above input groups\n display: block;\n width: @input-height-base;\n height: @input-height-base;\n line-height: @input-height-base;\n text-align: center;\n pointer-events: none;\n}\n.input-lg + .form-control-feedback {\n width: @input-height-large;\n height: @input-height-large;\n line-height: @input-height-large;\n}\n.input-sm + .form-control-feedback {\n width: @input-height-small;\n height: @input-height-small;\n line-height: @input-height-small;\n}\n\n// Feedback states\n.has-success {\n .form-control-validation(@state-success-text; @state-success-text; @state-success-bg);\n}\n.has-warning {\n .form-control-validation(@state-warning-text; @state-warning-text; @state-warning-bg);\n}\n.has-error {\n .form-control-validation(@state-danger-text; @state-danger-text; @state-danger-bg);\n}\n\n// Reposition feedback icon if input has visible label above\n.has-feedback label {\n\n & ~ .form-control-feedback {\n top: (@line-height-computed + 5); // Height of the `label` and its margin\n }\n &.sr-only ~ .form-control-feedback {\n top: 0;\n }\n}\n\n\n// Help text\n//\n// Apply to any element you wish to create light text for placement immediately\n// below a form control. Use for general help, formatting, or instructional text.\n\n.help-block {\n display: block; // account for any element using help-block\n margin-top: 5px;\n margin-bottom: 10px;\n color: lighten(@text-color, 25%); // lighten the text some for contrast\n}\n\n\n// Inline forms\n//\n// Make forms appear inline(-block) by adding the `.form-inline` class. Inline\n// forms begin stacked on extra small (mobile) devices and then go inline when\n// viewports reach <768px.\n//\n// Requires wrapping inputs and labels with `.form-group` for proper display of\n// default HTML form controls and our custom form controls (e.g., input groups).\n//\n// Heads up! This is mixin-ed into `.navbar-form` in navbars.less.\n\n.form-inline {\n\n // Kick in the inline\n @media (min-width: @screen-sm-min) {\n // Inline-block all the things for \"inline\"\n .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n\n // In navbar-form, allow folks to *not* use `.form-group`\n .form-control {\n display: inline-block;\n width: auto; // Prevent labels from stacking above inputs in `.form-group`\n vertical-align: middle;\n }\n\n // Make static controls behave like regular ones\n .form-control-static {\n display: inline-block;\n }\n\n .input-group {\n display: inline-table;\n vertical-align: middle;\n\n .input-group-addon,\n .input-group-btn,\n .form-control {\n width: auto;\n }\n }\n\n // Input groups need that 100% width though\n .input-group > .form-control {\n width: 100%;\n }\n\n .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n\n // Remove default margin on radios/checkboxes that were used for stacking, and\n // then undo the floating of radios and checkboxes to match.\n .radio,\n .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n\n label {\n padding-left: 0;\n }\n }\n .radio input[type=\"radio\"],\n .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n\n // Re-override the feedback icon.\n .has-feedback .form-control-feedback {\n top: 0;\n }\n }\n}\n\n\n// Horizontal forms\n//\n// Horizontal forms are built on grid classes and allow you to create forms with\n// labels on the left and inputs on the right.\n\n.form-horizontal {\n\n // Consistent vertical alignment of radios and checkboxes\n //\n // Labels also get some reset styles, but that is scoped to a media query below.\n .radio,\n .checkbox,\n .radio-inline,\n .checkbox-inline {\n margin-top: 0;\n margin-bottom: 0;\n padding-top: (@padding-base-vertical + 1); // Default padding plus a border\n }\n // Account for padding we're adding to ensure the alignment and of help text\n // and other content below items\n .radio,\n .checkbox {\n min-height: (@line-height-computed + (@padding-base-vertical + 1));\n }\n\n // Make form groups behave like rows\n .form-group {\n .make-row();\n }\n\n // Reset spacing and right align labels, but scope to media queries so that\n // labels on narrow viewports stack the same as a default form example.\n @media (min-width: @screen-sm-min) {\n .control-label {\n text-align: right;\n margin-bottom: 0;\n padding-top: (@padding-base-vertical + 1); // Default padding plus a border\n }\n }\n\n // Validation states\n //\n // Reposition the icon because it's now within a grid column and columns have\n // `position: relative;` on them. Also accounts for the grid gutter padding.\n .has-feedback .form-control-feedback {\n right: (@grid-gutter-width / 2);\n }\n\n // Form group sizes\n //\n // Quick utility class for applying `.input-lg` and `.input-sm` styles to the\n // inputs and labels within a `.form-group`.\n .form-group-lg {\n @media (min-width: @screen-sm-min) {\n .control-label {\n padding-top: ((@padding-large-vertical * @line-height-large) + 1);\n }\n }\n }\n .form-group-sm {\n @media (min-width: @screen-sm-min) {\n .control-label {\n padding-top: (@padding-small-vertical + 1);\n }\n }\n }\n}\n","// Form validation states\n//\n// Used in forms.less to generate the form validation CSS for warnings, errors,\n// and successes.\n\n.form-control-validation(@text-color: #555; @border-color: #ccc; @background-color: #f5f5f5) {\n // Color the label and help text\n .help-block,\n .control-label,\n .radio,\n .checkbox,\n .radio-inline,\n .checkbox-inline,\n &.radio label,\n &.checkbox label,\n &.radio-inline label,\n &.checkbox-inline label {\n color: @text-color;\n }\n // Set the border and box shadow on specific inputs to match\n .form-control {\n border-color: @border-color;\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work\n &:focus {\n border-color: darken(@border-color, 10%);\n @shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@border-color, 20%);\n .box-shadow(@shadow);\n }\n }\n // Set validation states also for addons\n .input-group-addon {\n color: @text-color;\n border-color: @border-color;\n background-color: @background-color;\n }\n // Optional feedback icon\n .form-control-feedback {\n color: @text-color;\n }\n}\n\n\n// Form control focus state\n//\n// Generate a customized focus state and for any input with the specified color,\n// which defaults to the `@input-border-focus` variable.\n//\n// We highly encourage you to not customize the default value, but instead use\n// this to tweak colors on an as-needed basis. This aesthetic change is based on\n// WebKit's default styles, but applicable to a wider range of browsers. Its\n// usability and accessibility should be taken into account with any change.\n//\n// Example usage: change the default blue border and shadow to white for better\n// contrast against a dark gray background.\n.form-control-focus(@color: @input-border-focus) {\n @color-rgba: rgba(red(@color), green(@color), blue(@color), .6);\n &:focus {\n border-color: @color;\n outline: 0;\n .box-shadow(~\"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px @{color-rgba}\");\n }\n}\n\n// Form control sizing\n//\n// Relative text size, padding, and border-radii changes for form controls. For\n// horizontal sizing, wrap controls in the predefined grid classes. `<select>`\n// element gets special love because it's special, and that's a fact!\n.input-size(@input-height; @padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n height: @input-height;\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n border-radius: @border-radius;\n\n select& {\n height: @input-height;\n line-height: @input-height;\n }\n\n textarea&,\n select[multiple]& {\n height: auto;\n }\n}\n","//\n// Buttons\n// --------------------------------------------------\n\n\n// Base styles\n// --------------------------------------------------\n\n.btn {\n display: inline-block;\n margin-bottom: 0; // For input.btn\n font-weight: @btn-font-weight;\n text-align: center;\n vertical-align: middle;\n touch-action: manipulation;\n cursor: pointer;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid transparent;\n white-space: nowrap;\n .button-size(@padding-base-vertical; @padding-base-horizontal; @font-size-base; @line-height-base; @border-radius-base);\n .user-select(none);\n\n &,\n &:active,\n &.active {\n &:focus,\n &.focus {\n .tab-focus();\n }\n }\n\n &:hover,\n &:focus,\n &.focus {\n color: @btn-default-color;\n text-decoration: none;\n }\n\n &:active,\n &.active {\n outline: 0;\n background-image: none;\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n cursor: @cursor-disabled;\n pointer-events: none; // Future-proof disabling of clicks\n .opacity(.65);\n .box-shadow(none);\n }\n}\n\n\n// Alternate buttons\n// --------------------------------------------------\n\n.btn-default {\n .button-variant(@btn-default-color; @btn-default-bg; @btn-default-border);\n}\n.btn-primary {\n .button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border);\n}\n// Success appears as green\n.btn-success {\n .button-variant(@btn-success-color; @btn-success-bg; @btn-success-border);\n}\n// Info appears as blue-green\n.btn-info {\n .button-variant(@btn-info-color; @btn-info-bg; @btn-info-border);\n}\n// Warning appears as orange\n.btn-warning {\n .button-variant(@btn-warning-color; @btn-warning-bg; @btn-warning-border);\n}\n// Danger and error appear as red\n.btn-danger {\n .button-variant(@btn-danger-color; @btn-danger-bg; @btn-danger-border);\n}\n\n\n// Link buttons\n// -------------------------\n\n// Make a button look and behave like a link\n.btn-link {\n color: @link-color;\n font-weight: normal;\n border-radius: 0;\n\n &,\n &:active,\n &.active,\n &[disabled],\n fieldset[disabled] & {\n background-color: transparent;\n .box-shadow(none);\n }\n &,\n &:hover,\n &:focus,\n &:active {\n border-color: transparent;\n }\n &:hover,\n &:focus {\n color: @link-hover-color;\n text-decoration: @link-hover-decoration;\n background-color: transparent;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @btn-link-disabled-color;\n text-decoration: none;\n }\n }\n}\n\n\n// Button Sizes\n// --------------------------------------------------\n\n.btn-lg {\n // line-height: ensure even-numbered height of button next to large input\n .button-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large);\n}\n.btn-sm {\n // line-height: ensure proper height of button next to small input\n .button-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small);\n}\n.btn-xs {\n .button-size(@padding-xs-vertical; @padding-xs-horizontal; @font-size-small; @line-height-small; @border-radius-small);\n}\n\n\n// Block button\n// --------------------------------------------------\n\n.btn-block {\n display: block;\n width: 100%;\n}\n\n// Vertically space out multiple block buttons\n.btn-block + .btn-block {\n margin-top: 5px;\n}\n\n// Specificity overrides\ninput[type=\"submit\"],\ninput[type=\"reset\"],\ninput[type=\"button\"] {\n &.btn-block {\n width: 100%;\n }\n}\n","// Button variants\n//\n// Easily pump out default styles, as well as :hover, :focus, :active,\n// and disabled options for all buttons\n\n.button-variant(@color; @background; @border) {\n color: @color;\n background-color: @background;\n border-color: @border;\n\n &:hover,\n &:focus,\n &.focus,\n &:active,\n &.active,\n .open > .dropdown-toggle& {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 12%);\n }\n &:active,\n &.active,\n .open > .dropdown-toggle& {\n background-image: none;\n }\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n &,\n &:hover,\n &:focus,\n &.focus,\n &:active,\n &.active {\n background-color: @background;\n border-color: @border;\n }\n }\n\n .badge {\n color: @background;\n background-color: @color;\n }\n}\n\n// Button sizes\n.button-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n border-radius: @border-radius;\n}\n","// Opacity\n\n.opacity(@opacity) {\n opacity: @opacity;\n // IE8 filter\n @opacity-ie: (@opacity * 100);\n filter: ~\"alpha(opacity=@{opacity-ie})\";\n}\n","//\n// Component animations\n// --------------------------------------------------\n\n// Heads up!\n//\n// We don't use the `.opacity()` mixin here since it causes a bug with text\n// fields in IE7-8. Source: https://github.com/twbs/bootstrap/pull/3552.\n\n.fade {\n opacity: 0;\n .transition(opacity .15s linear);\n &.in {\n opacity: 1;\n }\n}\n\n.collapse {\n display: none;\n\n &.in { display: block; }\n tr&.in { display: table-row; }\n tbody&.in { display: table-row-group; }\n}\n\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n .transition-property(~\"height, visibility\");\n .transition-duration(.35s);\n .transition-timing-function(ease);\n}\n","//\n// Dropdown menus\n// --------------------------------------------------\n\n\n// Dropdown arrow/caret\n.caret {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 2px;\n vertical-align: middle;\n border-top: @caret-width-base dashed;\n border-right: @caret-width-base solid transparent;\n border-left: @caret-width-base solid transparent;\n}\n\n// The dropdown wrapper (div)\n.dropup,\n.dropdown {\n position: relative;\n}\n\n// Prevent the focus on the dropdown toggle when closing dropdowns\n.dropdown-toggle:focus {\n outline: 0;\n}\n\n// The dropdown menu (ul)\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: @zindex-dropdown;\n display: none; // none by default, but block on \"open\" of the menu\n float: left;\n min-width: 160px;\n padding: 5px 0;\n margin: 2px 0 0; // override default ul\n list-style: none;\n font-size: @font-size-base;\n text-align: left; // Ensures proper alignment if parent has it changed (e.g., modal footer)\n background-color: @dropdown-bg;\n border: 1px solid @dropdown-fallback-border; // IE8 fallback\n border: 1px solid @dropdown-border;\n border-radius: @border-radius-base;\n .box-shadow(0 6px 12px rgba(0,0,0,.175));\n background-clip: padding-box;\n\n // Aligns the dropdown menu to right\n //\n // Deprecated as of 3.1.0 in favor of `.dropdown-menu-[dir]`\n &.pull-right {\n right: 0;\n left: auto;\n }\n\n // Dividers (basically an hr) within the dropdown\n .divider {\n .nav-divider(@dropdown-divider-bg);\n }\n\n // Links within the dropdown menu\n > li > a {\n display: block;\n padding: 3px 20px;\n clear: both;\n font-weight: normal;\n line-height: @line-height-base;\n color: @dropdown-link-color;\n white-space: nowrap; // prevent links from randomly breaking onto new lines\n }\n}\n\n// Hover/Focus state\n.dropdown-menu > li > a {\n &:hover,\n &:focus {\n text-decoration: none;\n color: @dropdown-link-hover-color;\n background-color: @dropdown-link-hover-bg;\n }\n}\n\n// Active state\n.dropdown-menu > .active > a {\n &,\n &:hover,\n &:focus {\n color: @dropdown-link-active-color;\n text-decoration: none;\n outline: 0;\n background-color: @dropdown-link-active-bg;\n }\n}\n\n// Disabled state\n//\n// Gray out text and ensure the hover/focus state remains gray\n\n.dropdown-menu > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @dropdown-link-disabled-color;\n }\n\n // Nuke hover/focus effects\n &:hover,\n &:focus {\n text-decoration: none;\n background-color: transparent;\n background-image: none; // Remove CSS gradient\n .reset-filter();\n cursor: @cursor-disabled;\n }\n}\n\n// Open state for the dropdown\n.open {\n // Show the menu\n > .dropdown-menu {\n display: block;\n }\n\n // Remove the outline when :focus is triggered\n > a {\n outline: 0;\n }\n}\n\n// Menu positioning\n//\n// Add extra class to `.dropdown-menu` to flip the alignment of the dropdown\n// menu with the parent.\n.dropdown-menu-right {\n left: auto; // Reset the default from `.dropdown-menu`\n right: 0;\n}\n// With v3, we enabled auto-flipping if you have a dropdown within a right\n// aligned nav component. To enable the undoing of that, we provide an override\n// to restore the default dropdown menu alignment.\n//\n// This is only for left-aligning a dropdown menu within a `.navbar-right` or\n// `.pull-right` nav component.\n.dropdown-menu-left {\n left: 0;\n right: auto;\n}\n\n// Dropdown section headers\n.dropdown-header {\n display: block;\n padding: 3px 20px;\n font-size: @font-size-small;\n line-height: @line-height-base;\n color: @dropdown-header-color;\n white-space: nowrap; // as with > li > a\n}\n\n// Backdrop to catch body clicks on mobile, etc.\n.dropdown-backdrop {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n top: 0;\n z-index: (@zindex-dropdown - 10);\n}\n\n// Right aligned dropdowns\n.pull-right > .dropdown-menu {\n right: 0;\n left: auto;\n}\n\n// Allow for dropdowns to go bottom up (aka, dropup-menu)\n//\n// Just add .dropup after the standard .dropdown class and you're set, bro.\n// TODO: abstract this so that the navbar fixed styles are not placed here?\n\n.dropup,\n.navbar-fixed-bottom .dropdown {\n // Reverse the caret\n .caret {\n border-top: 0;\n border-bottom: @caret-width-base solid;\n content: \"\";\n }\n // Different positioning for bottom up menu\n .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-bottom: 2px;\n }\n}\n\n\n// Component alignment\n//\n// Reiterate per navbar.less and the modified component alignment there.\n\n@media (min-width: @grid-float-breakpoint) {\n .navbar-right {\n .dropdown-menu {\n .dropdown-menu-right();\n }\n // Necessary for overrides of the default right aligned menu.\n // Will remove come v4 in all likelihood.\n .dropdown-menu-left {\n .dropdown-menu-left();\n }\n }\n}\n","// Horizontal dividers\n//\n// Dividers (basically an hr) within dropdowns and nav lists\n\n.nav-divider(@color: #e5e5e5) {\n height: 1px;\n margin: ((@line-height-computed / 2) - 1) 0;\n overflow: hidden;\n background-color: @color;\n}\n","// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n","//\n// Button groups\n// --------------------------------------------------\n\n// Make the div behave like a button\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-block;\n vertical-align: middle; // match .btn alignment given font-size hack above\n > .btn {\n position: relative;\n float: left;\n // Bring the \"active\" button to the front\n &:hover,\n &:focus,\n &:active,\n &.active {\n z-index: 2;\n }\n }\n}\n\n// Prevent double borders when buttons are next to each other\n.btn-group {\n .btn + .btn,\n .btn + .btn-group,\n .btn-group + .btn,\n .btn-group + .btn-group {\n margin-left: -1px;\n }\n}\n\n// Optional: Group multiple button groups together for a toolbar\n.btn-toolbar {\n margin-left: -5px; // Offset the first child's margin\n &:extend(.clearfix all);\n\n .btn-group,\n .input-group {\n float: left;\n }\n > .btn,\n > .btn-group,\n > .input-group {\n margin-left: 5px;\n }\n}\n\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n border-radius: 0;\n}\n\n// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match\n.btn-group > .btn:first-child {\n margin-left: 0;\n &:not(:last-child):not(.dropdown-toggle) {\n .border-right-radius(0);\n }\n}\n// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n .border-left-radius(0);\n}\n\n// Custom edits for including btn-groups within btn-groups (useful for including dropdown buttons within a btn-group)\n.btn-group > .btn-group {\n float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group > .btn-group:first-child:not(:last-child) {\n > .btn:last-child,\n > .dropdown-toggle {\n .border-right-radius(0);\n }\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n .border-left-radius(0);\n}\n\n// On active and open, don't show outline\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n outline: 0;\n}\n\n\n// Sizing\n//\n// Remix the default button sizing classes into new ones for easier manipulation.\n\n.btn-group-xs > .btn { &:extend(.btn-xs); }\n.btn-group-sm > .btn { &:extend(.btn-sm); }\n.btn-group-lg > .btn { &:extend(.btn-lg); }\n\n\n// Split button dropdowns\n// ----------------------\n\n// Give the line between buttons some depth\n.btn-group > .btn + .dropdown-toggle {\n padding-left: 8px;\n padding-right: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n padding-left: 12px;\n padding-right: 12px;\n}\n\n// The clickable button for toggling the menu\n// Remove the gradient and set the same inset shadow as the :active state\n.btn-group.open .dropdown-toggle {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n\n // Show no shadow for `.btn-link` since it has no other button styles.\n &.btn-link {\n .box-shadow(none);\n }\n}\n\n\n// Reposition the caret\n.btn .caret {\n margin-left: 0;\n}\n// Carets in other button sizes\n.btn-lg .caret {\n border-width: @caret-width-large @caret-width-large 0;\n border-bottom-width: 0;\n}\n// Upside down carets for .dropup\n.dropup .btn-lg .caret {\n border-width: 0 @caret-width-large @caret-width-large;\n}\n\n\n// Vertical button groups\n// ----------------------\n\n.btn-group-vertical {\n > .btn,\n > .btn-group,\n > .btn-group > .btn {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n }\n\n // Clear floats so dropdown menus can be properly placed\n > .btn-group {\n &:extend(.clearfix all);\n > .btn {\n float: none;\n }\n }\n\n > .btn + .btn,\n > .btn + .btn-group,\n > .btn-group + .btn,\n > .btn-group + .btn-group {\n margin-top: -1px;\n margin-left: 0;\n }\n}\n\n.btn-group-vertical > .btn {\n &:not(:first-child):not(:last-child) {\n border-radius: 0;\n }\n &:first-child:not(:last-child) {\n border-top-right-radius: @border-radius-base;\n .border-bottom-radius(0);\n }\n &:last-child:not(:first-child) {\n border-bottom-left-radius: @border-radius-base;\n .border-top-radius(0);\n }\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) {\n > .btn:last-child,\n > .dropdown-toggle {\n .border-bottom-radius(0);\n }\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n .border-top-radius(0);\n}\n\n\n// Justified button groups\n// ----------------------\n\n.btn-group-justified {\n display: table;\n width: 100%;\n table-layout: fixed;\n border-collapse: separate;\n > .btn,\n > .btn-group {\n float: none;\n display: table-cell;\n width: 1%;\n }\n > .btn-group .btn {\n width: 100%;\n }\n\n > .btn-group .dropdown-menu {\n left: auto;\n }\n}\n\n\n// Checkbox and radio options\n//\n// In order to support the browser's form validation feedback, powered by the\n// `required` attribute, we have to \"hide\" the inputs via `clip`. We cannot use\n// `display: none;` or `visibility: hidden;` as that also hides the popover.\n// Simply visually hiding the inputs via `opacity` would leave them clickable in\n// certain cases which is prevented by using `clip` and `pointer-events`.\n// This way, we ensure a DOM element is visible to position the popover from.\n//\n// See https://github.com/twbs/bootstrap/pull/12794 and\n// https://github.com/twbs/bootstrap/pull/14559 for more information.\n\n[data-toggle=\"buttons\"] {\n > .btn,\n > .btn-group > .btn {\n input[type=\"radio\"],\n input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0,0,0,0);\n pointer-events: none;\n }\n }\n}\n","// Single side border-radius\n\n.border-top-radius(@radius) {\n border-top-right-radius: @radius;\n border-top-left-radius: @radius;\n}\n.border-right-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-top-right-radius: @radius;\n}\n.border-bottom-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-bottom-left-radius: @radius;\n}\n.border-left-radius(@radius) {\n border-bottom-left-radius: @radius;\n border-top-left-radius: @radius;\n}\n","//\n// Input groups\n// --------------------------------------------------\n\n// Base styles\n// -------------------------\n.input-group {\n position: relative; // For dropdowns\n display: table;\n border-collapse: separate; // prevent input groups from inheriting border styles from table cells when placed within a table\n\n // Undo padding and float of grid classes\n &[class*=\"col-\"] {\n float: none;\n padding-left: 0;\n padding-right: 0;\n }\n\n .form-control {\n // Ensure that the input is always above the *appended* addon button for\n // proper border colors.\n position: relative;\n z-index: 2;\n\n // IE9 fubars the placeholder attribute in text inputs and the arrows on\n // select elements in input groups. To fix it, we float the input. Details:\n // https://github.com/twbs/bootstrap/issues/11561#issuecomment-28936855\n float: left;\n\n width: 100%;\n margin-bottom: 0;\n }\n}\n\n// Sizing options\n//\n// Remix the default form control sizing classes into new ones for easier\n// manipulation.\n\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n .input-lg();\n}\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n .input-sm();\n}\n\n\n// Display as table-cell\n// -------------------------\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n display: table-cell;\n\n &:not(:first-child):not(:last-child) {\n border-radius: 0;\n }\n}\n// Addon and addon wrapper for buttons\n.input-group-addon,\n.input-group-btn {\n width: 1%;\n white-space: nowrap;\n vertical-align: middle; // Match the inputs\n}\n\n// Text input groups\n// -------------------------\n.input-group-addon {\n padding: @padding-base-vertical @padding-base-horizontal;\n font-size: @font-size-base;\n font-weight: normal;\n line-height: 1;\n color: @input-color;\n text-align: center;\n background-color: @input-group-addon-bg;\n border: 1px solid @input-group-addon-border-color;\n border-radius: @border-radius-base;\n\n // Sizing\n &.input-sm {\n padding: @padding-small-vertical @padding-small-horizontal;\n font-size: @font-size-small;\n border-radius: @border-radius-small;\n }\n &.input-lg {\n padding: @padding-large-vertical @padding-large-horizontal;\n font-size: @font-size-large;\n border-radius: @border-radius-large;\n }\n\n // Nuke default margins from checkboxes and radios to vertically center within.\n input[type=\"radio\"],\n input[type=\"checkbox\"] {\n margin-top: 0;\n }\n}\n\n// Reset rounded corners\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n .border-right-radius(0);\n}\n.input-group-addon:first-child {\n border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n .border-left-radius(0);\n}\n.input-group-addon:last-child {\n border-left: 0;\n}\n\n// Button input groups\n// -------------------------\n.input-group-btn {\n position: relative;\n // Jankily prevent input button groups from wrapping with `white-space` and\n // `font-size` in combination with `inline-block` on buttons.\n font-size: 0;\n white-space: nowrap;\n\n // Negative margin for spacing, position for bringing hovered/focused/actived\n // element above the siblings.\n > .btn {\n position: relative;\n + .btn {\n margin-left: -1px;\n }\n // Bring the \"active\" button to the front\n &:hover,\n &:focus,\n &:active {\n z-index: 2;\n }\n }\n\n // Negative margin to only have a 1px border between the two\n &:first-child {\n > .btn,\n > .btn-group {\n margin-right: -1px;\n }\n }\n &:last-child {\n > .btn,\n > .btn-group {\n margin-left: -1px;\n }\n }\n}\n","//\n// Navs\n// --------------------------------------------------\n\n\n// Base class\n// --------------------------------------------------\n\n.nav {\n margin-bottom: 0;\n padding-left: 0; // Override default ul/ol\n list-style: none;\n &:extend(.clearfix all);\n\n > li {\n position: relative;\n display: block;\n\n > a {\n position: relative;\n display: block;\n padding: @nav-link-padding;\n &:hover,\n &:focus {\n text-decoration: none;\n background-color: @nav-link-hover-bg;\n }\n }\n\n // Disabled state sets text to gray and nukes hover/tab effects\n &.disabled > a {\n color: @nav-disabled-link-color;\n\n &:hover,\n &:focus {\n color: @nav-disabled-link-hover-color;\n text-decoration: none;\n background-color: transparent;\n cursor: @cursor-disabled;\n }\n }\n }\n\n // Open dropdowns\n .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @nav-link-hover-bg;\n border-color: @link-color;\n }\n }\n\n // Nav dividers (deprecated with v3.0.1)\n //\n // This should have been removed in v3 with the dropping of `.nav-list`, but\n // we missed it. We don't currently support this anywhere, but in the interest\n // of maintaining backward compatibility in case you use it, it's deprecated.\n .nav-divider {\n .nav-divider();\n }\n\n // Prevent IE8 from misplacing imgs\n //\n // See https://github.com/h5bp/html5-boilerplate/issues/984#issuecomment-3985989\n > li > a > img {\n max-width: none;\n }\n}\n\n\n// Tabs\n// -------------------------\n\n// Give the tabs something to sit on\n.nav-tabs {\n border-bottom: 1px solid @nav-tabs-border-color;\n > li {\n float: left;\n // Make the list-items overlay the bottom border\n margin-bottom: -1px;\n\n // Actual tabs (as links)\n > a {\n margin-right: 2px;\n line-height: @line-height-base;\n border: 1px solid transparent;\n border-radius: @border-radius-base @border-radius-base 0 0;\n &:hover {\n border-color: @nav-tabs-link-hover-border-color @nav-tabs-link-hover-border-color @nav-tabs-border-color;\n }\n }\n\n // Active state, and its :hover to override normal :hover\n &.active > a {\n &,\n &:hover,\n &:focus {\n color: @nav-tabs-active-link-hover-color;\n background-color: @nav-tabs-active-link-hover-bg;\n border: 1px solid @nav-tabs-active-link-hover-border-color;\n border-bottom-color: transparent;\n cursor: default;\n }\n }\n }\n // pulling this in mainly for less shorthand\n &.nav-justified {\n .nav-justified();\n .nav-tabs-justified();\n }\n}\n\n\n// Pills\n// -------------------------\n.nav-pills {\n > li {\n float: left;\n\n // Links rendered as pills\n > a {\n border-radius: @nav-pills-border-radius;\n }\n + li {\n margin-left: 2px;\n }\n\n // Active state\n &.active > a {\n &,\n &:hover,\n &:focus {\n color: @nav-pills-active-link-hover-color;\n background-color: @nav-pills-active-link-hover-bg;\n }\n }\n }\n}\n\n\n// Stacked pills\n.nav-stacked {\n > li {\n float: none;\n + li {\n margin-top: 2px;\n margin-left: 0; // no need for this gap between nav items\n }\n }\n}\n\n\n// Nav variations\n// --------------------------------------------------\n\n// Justified nav links\n// -------------------------\n\n.nav-justified {\n width: 100%;\n\n > li {\n float: none;\n > a {\n text-align: center;\n margin-bottom: 5px;\n }\n }\n\n > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n }\n\n @media (min-width: @screen-sm-min) {\n > li {\n display: table-cell;\n width: 1%;\n > a {\n margin-bottom: 0;\n }\n }\n }\n}\n\n// Move borders to anchors instead of bottom of list\n//\n// Mixin for adding on top the shared `.nav-justified` styles for our tabs\n.nav-tabs-justified {\n border-bottom: 0;\n\n > li > a {\n // Override margin from .nav-tabs\n margin-right: 0;\n border-radius: @border-radius-base;\n }\n\n > .active > a,\n > .active > a:hover,\n > .active > a:focus {\n border: 1px solid @nav-tabs-justified-link-border-color;\n }\n\n @media (min-width: @screen-sm-min) {\n > li > a {\n border-bottom: 1px solid @nav-tabs-justified-link-border-color;\n border-radius: @border-radius-base @border-radius-base 0 0;\n }\n > .active > a,\n > .active > a:hover,\n > .active > a:focus {\n border-bottom-color: @nav-tabs-justified-active-link-border-color;\n }\n }\n}\n\n\n// Tabbable tabs\n// -------------------------\n\n// Hide tabbable panes to start, show them when `.active`\n.tab-content {\n > .tab-pane {\n display: none;\n }\n > .active {\n display: block;\n }\n}\n\n\n// Dropdowns\n// -------------------------\n\n// Specific dropdowns\n.nav-tabs .dropdown-menu {\n // make dropdown border overlap tab border\n margin-top: -1px;\n // Remove the top rounded corners here since there is a hard edge above the menu\n .border-top-radius(0);\n}\n","//\n// Navbars\n// --------------------------------------------------\n\n\n// Wrapper and base class\n//\n// Provide a static navbar from which we expand to create full-width, fixed, and\n// other navbar variations.\n\n.navbar {\n position: relative;\n min-height: @navbar-height; // Ensure a navbar always shows (e.g., without a .navbar-brand in collapsed mode)\n margin-bottom: @navbar-margin-bottom;\n border: 1px solid transparent;\n\n // Prevent floats from breaking the navbar\n &:extend(.clearfix all);\n\n @media (min-width: @grid-float-breakpoint) {\n border-radius: @navbar-border-radius;\n }\n}\n\n\n// Navbar heading\n//\n// Groups `.navbar-brand` and `.navbar-toggle` into a single component for easy\n// styling of responsive aspects.\n\n.navbar-header {\n &:extend(.clearfix all);\n\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n }\n}\n\n\n// Navbar collapse (body)\n//\n// Group your navbar content into this for easy collapsing and expanding across\n// various device sizes. By default, this content is collapsed when <768px, but\n// will expand past that for a horizontal display.\n//\n// To start (on mobile devices) the navbar links, forms, and buttons are stacked\n// vertically and include a `max-height` to overflow in case you have too much\n// content for the user's viewport.\n\n.navbar-collapse {\n overflow-x: visible;\n padding-right: @navbar-padding-horizontal;\n padding-left: @navbar-padding-horizontal;\n border-top: 1px solid transparent;\n box-shadow: inset 0 1px 0 rgba(255,255,255,.1);\n &:extend(.clearfix all);\n -webkit-overflow-scrolling: touch;\n\n &.in {\n overflow-y: auto;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n width: auto;\n border-top: 0;\n box-shadow: none;\n\n &.collapse {\n display: block !important;\n height: auto !important;\n padding-bottom: 0; // Override default setting\n overflow: visible !important;\n }\n\n &.in {\n overflow-y: visible;\n }\n\n // Undo the collapse side padding for navbars with containers to ensure\n // alignment of right-aligned contents.\n .navbar-fixed-top &,\n .navbar-static-top &,\n .navbar-fixed-bottom & {\n padding-left: 0;\n padding-right: 0;\n }\n }\n}\n\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n .navbar-collapse {\n max-height: @navbar-collapse-max-height;\n\n @media (max-device-width: @screen-xs-min) and (orientation: landscape) {\n max-height: 200px;\n }\n }\n}\n\n\n// Both navbar header and collapse\n//\n// When a container is present, change the behavior of the header and collapse.\n\n.container,\n.container-fluid {\n > .navbar-header,\n > .navbar-collapse {\n margin-right: -@navbar-padding-horizontal;\n margin-left: -@navbar-padding-horizontal;\n\n @media (min-width: @grid-float-breakpoint) {\n margin-right: 0;\n margin-left: 0;\n }\n }\n}\n\n\n//\n// Navbar alignment options\n//\n// Display the navbar across the entirety of the page or fixed it to the top or\n// bottom of the page.\n\n// Static top (unfixed, but 100% wide) navbar\n.navbar-static-top {\n z-index: @zindex-navbar;\n border-width: 0 0 1px;\n\n @media (min-width: @grid-float-breakpoint) {\n border-radius: 0;\n }\n}\n\n// Fix the top/bottom navbars when screen real estate supports it\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n position: fixed;\n right: 0;\n left: 0;\n z-index: @zindex-navbar-fixed;\n\n // Undo the rounded corners\n @media (min-width: @grid-float-breakpoint) {\n border-radius: 0;\n }\n}\n.navbar-fixed-top {\n top: 0;\n border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n bottom: 0;\n margin-bottom: 0; // override .navbar defaults\n border-width: 1px 0 0;\n}\n\n\n// Brand/project name\n\n.navbar-brand {\n float: left;\n padding: @navbar-padding-vertical @navbar-padding-horizontal;\n font-size: @font-size-large;\n line-height: @line-height-computed;\n height: @navbar-height;\n\n &:hover,\n &:focus {\n text-decoration: none;\n }\n\n > img {\n display: block;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n .navbar > .container &,\n .navbar > .container-fluid & {\n margin-left: -@navbar-padding-horizontal;\n }\n }\n}\n\n\n// Navbar toggle\n//\n// Custom button for toggling the `.navbar-collapse`, powered by the collapse\n// JavaScript plugin.\n\n.navbar-toggle {\n position: relative;\n float: right;\n margin-right: @navbar-padding-horizontal;\n padding: 9px 10px;\n .navbar-vertical-align(34px);\n background-color: transparent;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid transparent;\n border-radius: @border-radius-base;\n\n // We remove the `outline` here, but later compensate by attaching `:hover`\n // styles to `:focus`.\n &:focus {\n outline: 0;\n }\n\n // Bars\n .icon-bar {\n display: block;\n width: 22px;\n height: 2px;\n border-radius: 1px;\n }\n .icon-bar + .icon-bar {\n margin-top: 4px;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n display: none;\n }\n}\n\n\n// Navbar nav links\n//\n// Builds on top of the `.nav` components with its own modifier class to make\n// the nav the full height of the horizontal nav (above 768px).\n\n.navbar-nav {\n margin: (@navbar-padding-vertical / 2) -@navbar-padding-horizontal;\n\n > li > a {\n padding-top: 10px;\n padding-bottom: 10px;\n line-height: @line-height-computed;\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display when collapsed\n .open .dropdown-menu {\n position: static;\n float: none;\n width: auto;\n margin-top: 0;\n background-color: transparent;\n border: 0;\n box-shadow: none;\n > li > a,\n .dropdown-header {\n padding: 5px 15px 5px 25px;\n }\n > li > a {\n line-height: @line-height-computed;\n &:hover,\n &:focus {\n background-image: none;\n }\n }\n }\n }\n\n // Uncollapse the nav\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n margin: 0;\n\n > li {\n float: left;\n > a {\n padding-top: @navbar-padding-vertical;\n padding-bottom: @navbar-padding-vertical;\n }\n }\n }\n}\n\n\n// Navbar form\n//\n// Extension of the `.form-inline` with some extra flavor for optimum display in\n// our navbars.\n\n.navbar-form {\n margin-left: -@navbar-padding-horizontal;\n margin-right: -@navbar-padding-horizontal;\n padding: 10px @navbar-padding-horizontal;\n border-top: 1px solid transparent;\n border-bottom: 1px solid transparent;\n @shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n\n // Mixin behavior for optimum display\n .form-inline();\n\n .form-group {\n @media (max-width: @grid-float-breakpoint-max) {\n margin-bottom: 5px;\n\n &:last-child {\n margin-bottom: 0;\n }\n }\n }\n\n // Vertically center in expanded, horizontal navbar\n .navbar-vertical-align(@input-height-base);\n\n // Undo 100% width for pull classes\n @media (min-width: @grid-float-breakpoint) {\n width: auto;\n border: 0;\n margin-left: 0;\n margin-right: 0;\n padding-top: 0;\n padding-bottom: 0;\n .box-shadow(none);\n }\n}\n\n\n// Dropdown menus\n\n// Menu position and menu carets\n.navbar-nav > li > .dropdown-menu {\n margin-top: 0;\n .border-top-radius(0);\n}\n// Menu position and menu caret support for dropups via extra dropup class\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n margin-bottom: 0;\n .border-top-radius(@navbar-border-radius);\n .border-bottom-radius(0);\n}\n\n\n// Buttons in navbars\n//\n// Vertically center a button within a navbar (when *not* in a form).\n\n.navbar-btn {\n .navbar-vertical-align(@input-height-base);\n\n &.btn-sm {\n .navbar-vertical-align(@input-height-small);\n }\n &.btn-xs {\n .navbar-vertical-align(22);\n }\n}\n\n\n// Text in navbars\n//\n// Add a class to make any element properly align itself vertically within the navbars.\n\n.navbar-text {\n .navbar-vertical-align(@line-height-computed);\n\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n margin-left: @navbar-padding-horizontal;\n margin-right: @navbar-padding-horizontal;\n }\n}\n\n\n// Component alignment\n//\n// Repurpose the pull utilities as their own navbar utilities to avoid specificity\n// issues with parents and chaining. Only do this when the navbar is uncollapsed\n// though so that navbar contents properly stack and align in mobile.\n//\n// Declared after the navbar components to ensure more specificity on the margins.\n\n@media (min-width: @grid-float-breakpoint) {\n .navbar-left { .pull-left(); }\n .navbar-right {\n .pull-right();\n margin-right: -@navbar-padding-horizontal;\n\n ~ .navbar-right {\n margin-right: 0;\n }\n }\n}\n\n\n// Alternate navbars\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n background-color: @navbar-default-bg;\n border-color: @navbar-default-border;\n\n .navbar-brand {\n color: @navbar-default-brand-color;\n &:hover,\n &:focus {\n color: @navbar-default-brand-hover-color;\n background-color: @navbar-default-brand-hover-bg;\n }\n }\n\n .navbar-text {\n color: @navbar-default-color;\n }\n\n .navbar-nav {\n > li > a {\n color: @navbar-default-link-color;\n\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n background-color: @navbar-default-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-active-color;\n background-color: @navbar-default-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n background-color: @navbar-default-link-disabled-bg;\n }\n }\n }\n\n .navbar-toggle {\n border-color: @navbar-default-toggle-border-color;\n &:hover,\n &:focus {\n background-color: @navbar-default-toggle-hover-bg;\n }\n .icon-bar {\n background-color: @navbar-default-toggle-icon-bar-bg;\n }\n }\n\n .navbar-collapse,\n .navbar-form {\n border-color: @navbar-default-border;\n }\n\n // Dropdown menu items\n .navbar-nav {\n // Remove background color from open dropdown\n > .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @navbar-default-link-active-bg;\n color: @navbar-default-link-active-color;\n }\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display when collapsed\n .open .dropdown-menu {\n > li > a {\n color: @navbar-default-link-color;\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n background-color: @navbar-default-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-active-color;\n background-color: @navbar-default-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n background-color: @navbar-default-link-disabled-bg;\n }\n }\n }\n }\n }\n\n\n // Links in navbars\n //\n // Add a class to ensure links outside the navbar nav are colored correctly.\n\n .navbar-link {\n color: @navbar-default-link-color;\n &:hover {\n color: @navbar-default-link-hover-color;\n }\n }\n\n .btn-link {\n color: @navbar-default-link-color;\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n }\n }\n }\n}\n\n// Inverse navbar\n\n.navbar-inverse {\n background-color: @navbar-inverse-bg;\n border-color: @navbar-inverse-border;\n\n .navbar-brand {\n color: @navbar-inverse-brand-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-brand-hover-color;\n background-color: @navbar-inverse-brand-hover-bg;\n }\n }\n\n .navbar-text {\n color: @navbar-inverse-color;\n }\n\n .navbar-nav {\n > li > a {\n color: @navbar-inverse-link-color;\n\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n background-color: @navbar-inverse-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-active-color;\n background-color: @navbar-inverse-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n background-color: @navbar-inverse-link-disabled-bg;\n }\n }\n }\n\n // Darken the responsive nav toggle\n .navbar-toggle {\n border-color: @navbar-inverse-toggle-border-color;\n &:hover,\n &:focus {\n background-color: @navbar-inverse-toggle-hover-bg;\n }\n .icon-bar {\n background-color: @navbar-inverse-toggle-icon-bar-bg;\n }\n }\n\n .navbar-collapse,\n .navbar-form {\n border-color: darken(@navbar-inverse-bg, 7%);\n }\n\n // Dropdowns\n .navbar-nav {\n > .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @navbar-inverse-link-active-bg;\n color: @navbar-inverse-link-active-color;\n }\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display\n .open .dropdown-menu {\n > .dropdown-header {\n border-color: @navbar-inverse-border;\n }\n .divider {\n background-color: @navbar-inverse-border;\n }\n > li > a {\n color: @navbar-inverse-link-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n background-color: @navbar-inverse-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-active-color;\n background-color: @navbar-inverse-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n background-color: @navbar-inverse-link-disabled-bg;\n }\n }\n }\n }\n }\n\n .navbar-link {\n color: @navbar-inverse-link-color;\n &:hover {\n color: @navbar-inverse-link-hover-color;\n }\n }\n\n .btn-link {\n color: @navbar-inverse-link-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n }\n }\n }\n}\n","// Navbar vertical align\n//\n// Vertically center elements in the navbar.\n// Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin.\n\n.navbar-vertical-align(@element-height) {\n margin-top: ((@navbar-height - @element-height) / 2);\n margin-bottom: ((@navbar-height - @element-height) / 2);\n}\n","//\n// Utility classes\n// --------------------------------------------------\n\n\n// Floats\n// -------------------------\n\n.clearfix {\n .clearfix();\n}\n.center-block {\n .center-block();\n}\n.pull-right {\n float: right !important;\n}\n.pull-left {\n float: left !important;\n}\n\n\n// Toggling content\n// -------------------------\n\n// Note: Deprecated .hide in favor of .hidden or .sr-only (as appropriate) in v3.0.1\n.hide {\n display: none !important;\n}\n.show {\n display: block !important;\n}\n.invisible {\n visibility: hidden;\n}\n.text-hide {\n .text-hide();\n}\n\n\n// Hide from screenreaders and browsers\n//\n// Credit: HTML5 Boilerplate\n\n.hidden {\n display: none !important;\n}\n\n\n// For Affix plugin\n// -------------------------\n\n.affix {\n position: fixed;\n}\n","//\n// Breadcrumbs\n// --------------------------------------------------\n\n\n.breadcrumb {\n padding: @breadcrumb-padding-vertical @breadcrumb-padding-horizontal;\n margin-bottom: @line-height-computed;\n list-style: none;\n background-color: @breadcrumb-bg;\n border-radius: @border-radius-base;\n\n > li {\n display: inline-block;\n\n + li:before {\n content: \"@{breadcrumb-separator}\\00a0\"; // Unicode space added since inline-block means non-collapsing white-space\n padding: 0 5px;\n color: @breadcrumb-color;\n }\n }\n\n > .active {\n color: @breadcrumb-active-color;\n }\n}\n","//\n// Pagination (multiple pages)\n// --------------------------------------------------\n.pagination {\n display: inline-block;\n padding-left: 0;\n margin: @line-height-computed 0;\n border-radius: @border-radius-base;\n\n > li {\n display: inline; // Remove list-style and block-level defaults\n > a,\n > span {\n position: relative;\n float: left; // Collapse white-space\n padding: @padding-base-vertical @padding-base-horizontal;\n line-height: @line-height-base;\n text-decoration: none;\n color: @pagination-color;\n background-color: @pagination-bg;\n border: 1px solid @pagination-border;\n margin-left: -1px;\n }\n &:first-child {\n > a,\n > span {\n margin-left: 0;\n .border-left-radius(@border-radius-base);\n }\n }\n &:last-child {\n > a,\n > span {\n .border-right-radius(@border-radius-base);\n }\n }\n }\n\n > li > a,\n > li > span {\n &:hover,\n &:focus {\n color: @pagination-hover-color;\n background-color: @pagination-hover-bg;\n border-color: @pagination-hover-border;\n }\n }\n\n > .active > a,\n > .active > span {\n &,\n &:hover,\n &:focus {\n z-index: 2;\n color: @pagination-active-color;\n background-color: @pagination-active-bg;\n border-color: @pagination-active-border;\n cursor: default;\n }\n }\n\n > .disabled {\n > span,\n > span:hover,\n > span:focus,\n > a,\n > a:hover,\n > a:focus {\n color: @pagination-disabled-color;\n background-color: @pagination-disabled-bg;\n border-color: @pagination-disabled-border;\n cursor: @cursor-disabled;\n }\n }\n}\n\n// Sizing\n// --------------------------------------------------\n\n// Large\n.pagination-lg {\n .pagination-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @border-radius-large);\n}\n\n// Small\n.pagination-sm {\n .pagination-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @border-radius-small);\n}\n","// Pagination\n\n.pagination-size(@padding-vertical; @padding-horizontal; @font-size; @border-radius) {\n > li {\n > a,\n > span {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n }\n &:first-child {\n > a,\n > span {\n .border-left-radius(@border-radius);\n }\n }\n &:last-child {\n > a,\n > span {\n .border-right-radius(@border-radius);\n }\n }\n }\n}\n","//\n// Pager pagination\n// --------------------------------------------------\n\n\n.pager {\n padding-left: 0;\n margin: @line-height-computed 0;\n list-style: none;\n text-align: center;\n &:extend(.clearfix all);\n li {\n display: inline;\n > a,\n > span {\n display: inline-block;\n padding: 5px 14px;\n background-color: @pager-bg;\n border: 1px solid @pager-border;\n border-radius: @pager-border-radius;\n }\n\n > a:hover,\n > a:focus {\n text-decoration: none;\n background-color: @pager-hover-bg;\n }\n }\n\n .next {\n > a,\n > span {\n float: right;\n }\n }\n\n .previous {\n > a,\n > span {\n float: left;\n }\n }\n\n .disabled {\n > a,\n > a:hover,\n > a:focus,\n > span {\n color: @pager-disabled-color;\n background-color: @pager-bg;\n cursor: @cursor-disabled;\n }\n }\n}\n","//\n// Labels\n// --------------------------------------------------\n\n.label {\n display: inline;\n padding: .2em .6em .3em;\n font-size: 75%;\n font-weight: bold;\n line-height: 1;\n color: @label-color;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: .25em;\n\n // Add hover effects, but only for links\n a& {\n &:hover,\n &:focus {\n color: @label-link-hover-color;\n text-decoration: none;\n cursor: pointer;\n }\n }\n\n // Empty labels collapse automatically (not available in IE8)\n &:empty {\n display: none;\n }\n\n // Quick fix for labels in buttons\n .btn & {\n position: relative;\n top: -1px;\n }\n}\n\n// Colors\n// Contextual variations (linked labels get darker on :hover)\n\n.label-default {\n .label-variant(@label-default-bg);\n}\n\n.label-primary {\n .label-variant(@label-primary-bg);\n}\n\n.label-success {\n .label-variant(@label-success-bg);\n}\n\n.label-info {\n .label-variant(@label-info-bg);\n}\n\n.label-warning {\n .label-variant(@label-warning-bg);\n}\n\n.label-danger {\n .label-variant(@label-danger-bg);\n}\n","// Labels\n\n.label-variant(@color) {\n background-color: @color;\n\n &[href] {\n &:hover,\n &:focus {\n background-color: darken(@color, 10%);\n }\n }\n}\n","//\n// Badges\n// --------------------------------------------------\n\n\n// Base class\n.badge {\n display: inline-block;\n min-width: 10px;\n padding: 3px 7px;\n font-size: @font-size-small;\n font-weight: @badge-font-weight;\n color: @badge-color;\n line-height: @badge-line-height;\n vertical-align: baseline;\n white-space: nowrap;\n text-align: center;\n background-color: @badge-bg;\n border-radius: @badge-border-radius;\n\n // Empty badges collapse automatically (not available in IE8)\n &:empty {\n display: none;\n }\n\n // Quick fix for badges in buttons\n .btn & {\n position: relative;\n top: -1px;\n }\n\n .btn-xs &,\n .btn-group-xs > .btn & {\n top: 0;\n padding: 1px 5px;\n }\n\n // Hover state, but only for links\n a& {\n &:hover,\n &:focus {\n color: @badge-link-hover-color;\n text-decoration: none;\n cursor: pointer;\n }\n }\n\n // Account for badges in navs\n .list-group-item.active > &,\n .nav-pills > .active > a > & {\n color: @badge-active-color;\n background-color: @badge-active-bg;\n }\n\n .list-group-item > & {\n float: right;\n }\n\n .list-group-item > & + & {\n margin-right: 5px;\n }\n\n .nav-pills > li > a > & {\n margin-left: 3px;\n }\n}\n","//\n// Jumbotron\n// --------------------------------------------------\n\n\n.jumbotron {\n padding: @jumbotron-padding (@jumbotron-padding / 2);\n margin-bottom: @jumbotron-padding;\n color: @jumbotron-color;\n background-color: @jumbotron-bg;\n\n h1,\n .h1 {\n color: @jumbotron-heading-color;\n }\n\n p {\n margin-bottom: (@jumbotron-padding / 2);\n font-size: @jumbotron-font-size;\n font-weight: 200;\n }\n\n > hr {\n border-top-color: darken(@jumbotron-bg, 10%);\n }\n\n .container &,\n .container-fluid & {\n border-radius: @border-radius-large; // Only round corners at higher resolutions if contained in a container\n }\n\n .container {\n max-width: 100%;\n }\n\n @media screen and (min-width: @screen-sm-min) {\n padding: (@jumbotron-padding * 1.6) 0;\n\n .container &,\n .container-fluid & {\n padding-left: (@jumbotron-padding * 2);\n padding-right: (@jumbotron-padding * 2);\n }\n\n h1,\n .h1 {\n font-size: (@font-size-base * 4.5);\n }\n }\n}\n","//\n// Thumbnails\n// --------------------------------------------------\n\n\n// Mixin and adjust the regular image class\n.thumbnail {\n display: block;\n padding: @thumbnail-padding;\n margin-bottom: @line-height-computed;\n line-height: @line-height-base;\n background-color: @thumbnail-bg;\n border: 1px solid @thumbnail-border;\n border-radius: @thumbnail-border-radius;\n .transition(border .2s ease-in-out);\n\n > img,\n a > img {\n &:extend(.img-responsive);\n margin-left: auto;\n margin-right: auto;\n }\n\n // Add a hover state for linked versions only\n a&:hover,\n a&:focus,\n a&.active {\n border-color: @link-color;\n }\n\n // Image captions\n .caption {\n padding: @thumbnail-caption-padding;\n color: @thumbnail-caption-color;\n }\n}\n","//\n// Alerts\n// --------------------------------------------------\n\n\n// Base styles\n// -------------------------\n\n.alert {\n padding: @alert-padding;\n margin-bottom: @line-height-computed;\n border: 1px solid transparent;\n border-radius: @alert-border-radius;\n\n // Headings for larger alerts\n h4 {\n margin-top: 0;\n // Specified for the h4 to prevent conflicts of changing @headings-color\n color: inherit;\n }\n\n // Provide class for links that match alerts\n .alert-link {\n font-weight: @alert-link-font-weight;\n }\n\n // Improve alignment and spacing of inner content\n > p,\n > ul {\n margin-bottom: 0;\n }\n\n > p + p {\n margin-top: 5px;\n }\n}\n\n// Dismissible alerts\n//\n// Expand the right padding and account for the close button's positioning.\n\n.alert-dismissable, // The misspelled .alert-dismissable was deprecated in 3.2.0.\n.alert-dismissible {\n padding-right: (@alert-padding + 20);\n\n // Adjust close link position\n .close {\n position: relative;\n top: -2px;\n right: -21px;\n color: inherit;\n }\n}\n\n// Alternate styles\n//\n// Generate contextual modifier classes for colorizing the alert.\n\n.alert-success {\n .alert-variant(@alert-success-bg; @alert-success-border; @alert-success-text);\n}\n\n.alert-info {\n .alert-variant(@alert-info-bg; @alert-info-border; @alert-info-text);\n}\n\n.alert-warning {\n .alert-variant(@alert-warning-bg; @alert-warning-border; @alert-warning-text);\n}\n\n.alert-danger {\n .alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text);\n}\n","// Alerts\n\n.alert-variant(@background; @border; @text-color) {\n background-color: @background;\n border-color: @border;\n color: @text-color;\n\n hr {\n border-top-color: darken(@border, 5%);\n }\n .alert-link {\n color: darken(@text-color, 10%);\n }\n}\n","//\n// Progress bars\n// --------------------------------------------------\n\n\n// Bar animations\n// -------------------------\n\n// WebKit\n@-webkit-keyframes progress-bar-stripes {\n from { background-position: 40px 0; }\n to { background-position: 0 0; }\n}\n\n// Spec and IE10+\n@keyframes progress-bar-stripes {\n from { background-position: 40px 0; }\n to { background-position: 0 0; }\n}\n\n\n// Bar itself\n// -------------------------\n\n// Outer container\n.progress {\n overflow: hidden;\n height: @line-height-computed;\n margin-bottom: @line-height-computed;\n background-color: @progress-bg;\n border-radius: @progress-border-radius;\n .box-shadow(inset 0 1px 2px rgba(0,0,0,.1));\n}\n\n// Bar of progress\n.progress-bar {\n float: left;\n width: 0%;\n height: 100%;\n font-size: @font-size-small;\n line-height: @line-height-computed;\n color: @progress-bar-color;\n text-align: center;\n background-color: @progress-bar-bg;\n .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15));\n .transition(width .6s ease);\n}\n\n// Striped bars\n//\n// `.progress-striped .progress-bar` is deprecated as of v3.2.0 in favor of the\n// `.progress-bar-striped` class, which you just add to an existing\n// `.progress-bar`.\n.progress-striped .progress-bar,\n.progress-bar-striped {\n #gradient > .striped();\n background-size: 40px 40px;\n}\n\n// Call animation for the active one\n//\n// `.progress.active .progress-bar` is deprecated as of v3.2.0 in favor of the\n// `.progress-bar.active` approach.\n.progress.active .progress-bar,\n.progress-bar.active {\n .animation(progress-bar-stripes 2s linear infinite);\n}\n\n\n// Variations\n// -------------------------\n\n.progress-bar-success {\n .progress-bar-variant(@progress-bar-success-bg);\n}\n\n.progress-bar-info {\n .progress-bar-variant(@progress-bar-info-bg);\n}\n\n.progress-bar-warning {\n .progress-bar-variant(@progress-bar-warning-bg);\n}\n\n.progress-bar-danger {\n .progress-bar-variant(@progress-bar-danger-bg);\n}\n","// Gradients\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n","// Progress bars\n\n.progress-bar-variant(@color) {\n background-color: @color;\n\n // Deprecated parent class requirement as of v3.2.0\n .progress-striped & {\n #gradient > .striped();\n }\n}\n",".media {\n // Proper spacing between instances of .media\n margin-top: 15px;\n\n &:first-child {\n margin-top: 0;\n }\n}\n\n.media,\n.media-body {\n zoom: 1;\n overflow: hidden;\n}\n\n.media-body {\n width: 10000px;\n}\n\n.media-object {\n display: block;\n}\n\n.media-right,\n.media > .pull-right {\n padding-left: 10px;\n}\n\n.media-left,\n.media > .pull-left {\n padding-right: 10px;\n}\n\n.media-left,\n.media-right,\n.media-body {\n display: table-cell;\n vertical-align: top;\n}\n\n.media-middle {\n vertical-align: middle;\n}\n\n.media-bottom {\n vertical-align: bottom;\n}\n\n// Reset margins on headings for tighter default spacing\n.media-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n\n// Media list variation\n//\n// Undo default ul/ol styles\n.media-list {\n padding-left: 0;\n list-style: none;\n}\n","//\n// List groups\n// --------------------------------------------------\n\n\n// Base class\n//\n// Easily usable on <ul>, <ol>, or <div>.\n\n.list-group {\n // No need to set list-style: none; since .list-group-item is block level\n margin-bottom: 20px;\n padding-left: 0; // reset padding because ul and ol\n}\n\n\n// Individual list items\n//\n// Use on `li`s or `div`s within the `.list-group` parent.\n\n.list-group-item {\n position: relative;\n display: block;\n padding: 10px 15px;\n // Place the border on the list items and negative margin up for better styling\n margin-bottom: -1px;\n background-color: @list-group-bg;\n border: 1px solid @list-group-border;\n\n // Round the first and last items\n &:first-child {\n .border-top-radius(@list-group-border-radius);\n }\n &:last-child {\n margin-bottom: 0;\n .border-bottom-radius(@list-group-border-radius);\n }\n}\n\n\n// Linked list items\n//\n// Use anchor elements instead of `li`s or `div`s to create linked list items.\n// Includes an extra `.active` modifier class for showing selected items.\n\na.list-group-item {\n color: @list-group-link-color;\n\n .list-group-item-heading {\n color: @list-group-link-heading-color;\n }\n\n // Hover state\n &:hover,\n &:focus {\n text-decoration: none;\n color: @list-group-link-hover-color;\n background-color: @list-group-hover-bg;\n }\n}\n\n.list-group-item {\n // Disabled state\n &.disabled,\n &.disabled:hover,\n &.disabled:focus {\n background-color: @list-group-disabled-bg;\n color: @list-group-disabled-color;\n cursor: @cursor-disabled;\n\n // Force color to inherit for custom content\n .list-group-item-heading {\n color: inherit;\n }\n .list-group-item-text {\n color: @list-group-disabled-text-color;\n }\n }\n\n // Active class on item itself, not parent\n &.active,\n &.active:hover,\n &.active:focus {\n z-index: 2; // Place active items above their siblings for proper border styling\n color: @list-group-active-color;\n background-color: @list-group-active-bg;\n border-color: @list-group-active-border;\n\n // Force color to inherit for custom content\n .list-group-item-heading,\n .list-group-item-heading > small,\n .list-group-item-heading > .small {\n color: inherit;\n }\n .list-group-item-text {\n color: @list-group-active-text-color;\n }\n }\n}\n\n\n// Contextual variants\n//\n// Add modifier classes to change text and background color on individual items.\n// Organizationally, this must come after the `:hover` states.\n\n.list-group-item-variant(success; @state-success-bg; @state-success-text);\n.list-group-item-variant(info; @state-info-bg; @state-info-text);\n.list-group-item-variant(warning; @state-warning-bg; @state-warning-text);\n.list-group-item-variant(danger; @state-danger-bg; @state-danger-text);\n\n\n// Custom content options\n//\n// Extra classes for creating well-formatted content within `.list-group-item`s.\n\n.list-group-item-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.list-group-item-text {\n margin-bottom: 0;\n line-height: 1.3;\n}\n","// List Groups\n\n.list-group-item-variant(@state; @background; @color) {\n .list-group-item-@{state} {\n color: @color;\n background-color: @background;\n\n a& {\n color: @color;\n\n .list-group-item-heading {\n color: inherit;\n }\n\n &:hover,\n &:focus {\n color: @color;\n background-color: darken(@background, 5%);\n }\n &.active,\n &.active:hover,\n &.active:focus {\n color: #fff;\n background-color: @color;\n border-color: @color;\n }\n }\n }\n}\n","//\n// Panels\n// --------------------------------------------------\n\n\n// Base class\n.panel {\n margin-bottom: @line-height-computed;\n background-color: @panel-bg;\n border: 1px solid transparent;\n border-radius: @panel-border-radius;\n .box-shadow(0 1px 1px rgba(0,0,0,.05));\n}\n\n// Panel contents\n.panel-body {\n padding: @panel-body-padding;\n &:extend(.clearfix all);\n}\n\n// Optional heading\n.panel-heading {\n padding: @panel-heading-padding;\n border-bottom: 1px solid transparent;\n .border-top-radius((@panel-border-radius - 1));\n\n > .dropdown .dropdown-toggle {\n color: inherit;\n }\n}\n\n// Within heading, strip any `h*` tag of its default margins for spacing.\n.panel-title {\n margin-top: 0;\n margin-bottom: 0;\n font-size: ceil((@font-size-base * 1.125));\n color: inherit;\n\n > a,\n > small,\n > .small,\n > small > a,\n > .small > a {\n color: inherit;\n }\n}\n\n// Optional footer (stays gray in every modifier class)\n.panel-footer {\n padding: @panel-footer-padding;\n background-color: @panel-footer-bg;\n border-top: 1px solid @panel-inner-border;\n .border-bottom-radius((@panel-border-radius - 1));\n}\n\n\n// List groups in panels\n//\n// By default, space out list group content from panel headings to account for\n// any kind of custom content between the two.\n\n.panel {\n > .list-group,\n > .panel-collapse > .list-group {\n margin-bottom: 0;\n\n .list-group-item {\n border-width: 1px 0;\n border-radius: 0;\n }\n\n // Add border top radius for first one\n &:first-child {\n .list-group-item:first-child {\n border-top: 0;\n .border-top-radius((@panel-border-radius - 1));\n }\n }\n // Add border bottom radius for last one\n &:last-child {\n .list-group-item:last-child {\n border-bottom: 0;\n .border-bottom-radius((@panel-border-radius - 1));\n }\n }\n }\n}\n// Collapse space between when there's no additional content.\n.panel-heading + .list-group {\n .list-group-item:first-child {\n border-top-width: 0;\n }\n}\n.list-group + .panel-footer {\n border-top-width: 0;\n}\n\n// Tables in panels\n//\n// Place a non-bordered `.table` within a panel (not within a `.panel-body`) and\n// watch it go full width.\n\n.panel {\n > .table,\n > .table-responsive > .table,\n > .panel-collapse > .table {\n margin-bottom: 0;\n\n caption {\n padding-left: @panel-body-padding;\n padding-right: @panel-body-padding;\n }\n }\n // Add border top radius for first one\n > .table:first-child,\n > .table-responsive:first-child > .table:first-child {\n .border-top-radius((@panel-border-radius - 1));\n\n > thead:first-child,\n > tbody:first-child {\n > tr:first-child {\n border-top-left-radius: (@panel-border-radius - 1);\n border-top-right-radius: (@panel-border-radius - 1);\n\n td:first-child,\n th:first-child {\n border-top-left-radius: (@panel-border-radius - 1);\n }\n td:last-child,\n th:last-child {\n border-top-right-radius: (@panel-border-radius - 1);\n }\n }\n }\n }\n // Add border bottom radius for last one\n > .table:last-child,\n > .table-responsive:last-child > .table:last-child {\n .border-bottom-radius((@panel-border-radius - 1));\n\n > tbody:last-child,\n > tfoot:last-child {\n > tr:last-child {\n border-bottom-left-radius: (@panel-border-radius - 1);\n border-bottom-right-radius: (@panel-border-radius - 1);\n\n td:first-child,\n th:first-child {\n border-bottom-left-radius: (@panel-border-radius - 1);\n }\n td:last-child,\n th:last-child {\n border-bottom-right-radius: (@panel-border-radius - 1);\n }\n }\n }\n }\n > .panel-body + .table,\n > .panel-body + .table-responsive,\n > .table + .panel-body,\n > .table-responsive + .panel-body {\n border-top: 1px solid @table-border-color;\n }\n > .table > tbody:first-child > tr:first-child th,\n > .table > tbody:first-child > tr:first-child td {\n border-top: 0;\n }\n > .table-bordered,\n > .table-responsive > .table-bordered {\n border: 0;\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th:first-child,\n > td:first-child {\n border-left: 0;\n }\n > th:last-child,\n > td:last-child {\n border-right: 0;\n }\n }\n }\n > thead,\n > tbody {\n > tr:first-child {\n > td,\n > th {\n border-bottom: 0;\n }\n }\n }\n > tbody,\n > tfoot {\n > tr:last-child {\n > td,\n > th {\n border-bottom: 0;\n }\n }\n }\n }\n > .table-responsive {\n border: 0;\n margin-bottom: 0;\n }\n}\n\n\n// Collapsable panels (aka, accordion)\n//\n// Wrap a series of panels in `.panel-group` to turn them into an accordion with\n// the help of our collapse JavaScript plugin.\n\n.panel-group {\n margin-bottom: @line-height-computed;\n\n // Tighten up margin so it's only between panels\n .panel {\n margin-bottom: 0;\n border-radius: @panel-border-radius;\n\n + .panel {\n margin-top: 5px;\n }\n }\n\n .panel-heading {\n border-bottom: 0;\n\n + .panel-collapse > .panel-body,\n + .panel-collapse > .list-group {\n border-top: 1px solid @panel-inner-border;\n }\n }\n\n .panel-footer {\n border-top: 0;\n + .panel-collapse .panel-body {\n border-bottom: 1px solid @panel-inner-border;\n }\n }\n}\n\n\n// Contextual variations\n.panel-default {\n .panel-variant(@panel-default-border; @panel-default-text; @panel-default-heading-bg; @panel-default-border);\n}\n.panel-primary {\n .panel-variant(@panel-primary-border; @panel-primary-text; @panel-primary-heading-bg; @panel-primary-border);\n}\n.panel-success {\n .panel-variant(@panel-success-border; @panel-success-text; @panel-success-heading-bg; @panel-success-border);\n}\n.panel-info {\n .panel-variant(@panel-info-border; @panel-info-text; @panel-info-heading-bg; @panel-info-border);\n}\n.panel-warning {\n .panel-variant(@panel-warning-border; @panel-warning-text; @panel-warning-heading-bg; @panel-warning-border);\n}\n.panel-danger {\n .panel-variant(@panel-danger-border; @panel-danger-text; @panel-danger-heading-bg; @panel-danger-border);\n}\n","// Panels\n\n.panel-variant(@border; @heading-text-color; @heading-bg-color; @heading-border) {\n border-color: @border;\n\n & > .panel-heading {\n color: @heading-text-color;\n background-color: @heading-bg-color;\n border-color: @heading-border;\n\n + .panel-collapse > .panel-body {\n border-top-color: @border;\n }\n .badge {\n color: @heading-bg-color;\n background-color: @heading-text-color;\n }\n }\n & > .panel-footer {\n + .panel-collapse > .panel-body {\n border-bottom-color: @border;\n }\n }\n}\n","// Embeds responsive\n//\n// Credit: Nicolas Gallagher and SUIT CSS.\n\n.embed-responsive {\n position: relative;\n display: block;\n height: 0;\n padding: 0;\n overflow: hidden;\n\n .embed-responsive-item,\n iframe,\n embed,\n object,\n video {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n height: 100%;\n width: 100%;\n border: 0;\n }\n}\n\n// Modifier class for 16:9 aspect ratio\n.embed-responsive-16by9 {\n padding-bottom: 56.25%;\n}\n\n// Modifier class for 4:3 aspect ratio\n.embed-responsive-4by3 {\n padding-bottom: 75%;\n}\n","//\n// Wells\n// --------------------------------------------------\n\n\n// Base class\n.well {\n min-height: 20px;\n padding: 19px;\n margin-bottom: 20px;\n background-color: @well-bg;\n border: 1px solid @well-border;\n border-radius: @border-radius-base;\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.05));\n blockquote {\n border-color: #ddd;\n border-color: rgba(0,0,0,.15);\n }\n}\n\n// Sizes\n.well-lg {\n padding: 24px;\n border-radius: @border-radius-large;\n}\n.well-sm {\n padding: 9px;\n border-radius: @border-radius-small;\n}\n","//\n// Close icons\n// --------------------------------------------------\n\n\n.close {\n float: right;\n font-size: (@font-size-base * 1.5);\n font-weight: @close-font-weight;\n line-height: 1;\n color: @close-color;\n text-shadow: @close-text-shadow;\n .opacity(.2);\n\n &:hover,\n &:focus {\n color: @close-color;\n text-decoration: none;\n cursor: pointer;\n .opacity(.5);\n }\n\n // Additional properties for button version\n // iOS requires the button element instead of an anchor tag.\n // If you want the anchor version, it requires `href=\"#\"`.\n // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile\n button& {\n padding: 0;\n cursor: pointer;\n background: transparent;\n border: 0;\n -webkit-appearance: none;\n }\n}\n","//\n// Modals\n// --------------------------------------------------\n\n// .modal-open - body class for killing the scroll\n// .modal - container to scroll within\n// .modal-dialog - positioning shell for the actual modal\n// .modal-content - actual modal w/ bg and corners and shit\n\n// Kill the scroll on the body\n.modal-open {\n overflow: hidden;\n}\n\n// Container that the modal scrolls within\n.modal {\n display: none;\n overflow: hidden;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: @zindex-modal;\n -webkit-overflow-scrolling: touch;\n\n // Prevent Chrome on Windows from adding a focus outline. For details, see\n // https://github.com/twbs/bootstrap/pull/10951.\n outline: 0;\n\n // When fading in the modal, animate it to slide down\n &.fade .modal-dialog {\n .translate(0, -25%);\n .transition-transform(~\"0.3s ease-out\");\n }\n &.in .modal-dialog { .translate(0, 0) }\n}\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n\n// Shell div to position the modal with bottom padding\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 10px;\n}\n\n// Actual modal\n.modal-content {\n position: relative;\n background-color: @modal-content-bg;\n border: 1px solid @modal-content-fallback-border-color; //old browsers fallback (ie8 etc)\n border: 1px solid @modal-content-border-color;\n border-radius: @border-radius-large;\n .box-shadow(0 3px 9px rgba(0,0,0,.5));\n background-clip: padding-box;\n // Remove focus outline from opened modal\n outline: 0;\n}\n\n// Modal background\n.modal-backdrop {\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: @zindex-modal-background;\n background-color: @modal-backdrop-bg;\n // Fade for backdrop\n &.fade { .opacity(0); }\n &.in { .opacity(@modal-backdrop-opacity); }\n}\n\n// Modal header\n// Top section of the modal w/ title and dismiss\n.modal-header {\n padding: @modal-title-padding;\n border-bottom: 1px solid @modal-header-border-color;\n min-height: (@modal-title-padding + @modal-title-line-height);\n}\n// Close icon\n.modal-header .close {\n margin-top: -2px;\n}\n\n// Title text within header\n.modal-title {\n margin: 0;\n line-height: @modal-title-line-height;\n}\n\n// Modal body\n// Where all modal content resides (sibling of .modal-header and .modal-footer)\n.modal-body {\n position: relative;\n padding: @modal-inner-padding;\n}\n\n// Footer (for actions)\n.modal-footer {\n padding: @modal-inner-padding;\n text-align: right; // right align buttons\n border-top: 1px solid @modal-footer-border-color;\n &:extend(.clearfix all); // clear it in case folks use .pull-* classes on buttons\n\n // Properly space out buttons\n .btn + .btn {\n margin-left: 5px;\n margin-bottom: 0; // account for input[type=\"submit\"] which gets the bottom margin like all other inputs\n }\n // but override that for button groups\n .btn-group .btn + .btn {\n margin-left: -1px;\n }\n // and override it for block buttons as well\n .btn-block + .btn-block {\n margin-left: 0;\n }\n}\n\n// Measure scrollbar width for padding body during modal show/hide\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n\n// Scale up the modal\n@media (min-width: @screen-sm-min) {\n // Automatically set modal's width for larger viewports\n .modal-dialog {\n width: @modal-md;\n margin: 30px auto;\n }\n .modal-content {\n .box-shadow(0 5px 15px rgba(0,0,0,.5));\n }\n\n // Modal sizes\n .modal-sm { width: @modal-sm; }\n}\n\n@media (min-width: @screen-md-min) {\n .modal-lg { width: @modal-lg; }\n}\n","//\n// Tooltips\n// --------------------------------------------------\n\n\n// Base class\n.tooltip {\n position: absolute;\n z-index: @zindex-tooltip;\n display: block;\n // Reset font and text properties given new insertion method\n font-family: @font-family-base;\n font-size: @font-size-small;\n font-weight: normal;\n line-height: 1.4;\n .opacity(0);\n\n &.in { .opacity(@tooltip-opacity); }\n &.top { margin-top: -3px; padding: @tooltip-arrow-width 0; }\n &.right { margin-left: 3px; padding: 0 @tooltip-arrow-width; }\n &.bottom { margin-top: 3px; padding: @tooltip-arrow-width 0; }\n &.left { margin-left: -3px; padding: 0 @tooltip-arrow-width; }\n}\n\n// Wrapper for the tooltip content\n.tooltip-inner {\n max-width: @tooltip-max-width;\n padding: 3px 8px;\n color: @tooltip-color;\n text-align: center;\n text-decoration: none;\n background-color: @tooltip-bg;\n border-radius: @border-radius-base;\n}\n\n// Arrows\n.tooltip-arrow {\n position: absolute;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n// Note: Deprecated .top-left, .top-right, .bottom-left, and .bottom-right as of v3.3.1\n.tooltip {\n &.top .tooltip-arrow {\n bottom: 0;\n left: 50%;\n margin-left: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n border-top-color: @tooltip-arrow-color;\n }\n &.top-left .tooltip-arrow {\n bottom: 0;\n right: @tooltip-arrow-width;\n margin-bottom: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n border-top-color: @tooltip-arrow-color;\n }\n &.top-right .tooltip-arrow {\n bottom: 0;\n left: @tooltip-arrow-width;\n margin-bottom: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n border-top-color: @tooltip-arrow-color;\n }\n &.right .tooltip-arrow {\n top: 50%;\n left: 0;\n margin-top: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width @tooltip-arrow-width 0;\n border-right-color: @tooltip-arrow-color;\n }\n &.left .tooltip-arrow {\n top: 50%;\n right: 0;\n margin-top: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-left-color: @tooltip-arrow-color;\n }\n &.bottom .tooltip-arrow {\n top: 0;\n left: 50%;\n margin-left: -@tooltip-arrow-width;\n border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-bottom-color: @tooltip-arrow-color;\n }\n &.bottom-left .tooltip-arrow {\n top: 0;\n right: @tooltip-arrow-width;\n margin-top: -@tooltip-arrow-width;\n border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-bottom-color: @tooltip-arrow-color;\n }\n &.bottom-right .tooltip-arrow {\n top: 0;\n left: @tooltip-arrow-width;\n margin-top: -@tooltip-arrow-width;\n border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-bottom-color: @tooltip-arrow-color;\n }\n}\n","//\n// Popovers\n// --------------------------------------------------\n\n\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: @zindex-popover;\n display: none;\n max-width: @popover-max-width;\n padding: 1px;\n // Reset font and text properties given new insertion method\n font-family: @font-family-base;\n font-size: @font-size-base;\n font-weight: normal;\n line-height: @line-height-base;\n text-align: left;\n background-color: @popover-bg;\n background-clip: padding-box;\n border: 1px solid @popover-fallback-border-color;\n border: 1px solid @popover-border-color;\n border-radius: @border-radius-large;\n .box-shadow(0 5px 10px rgba(0,0,0,.2));\n\n // Overrides for proper insertion\n white-space: normal;\n\n // Offset the popover to account for the popover arrow\n &.top { margin-top: -@popover-arrow-width; }\n &.right { margin-left: @popover-arrow-width; }\n &.bottom { margin-top: @popover-arrow-width; }\n &.left { margin-left: -@popover-arrow-width; }\n}\n\n.popover-title {\n margin: 0; // reset heading margin\n padding: 8px 14px;\n font-size: @font-size-base;\n background-color: @popover-title-bg;\n border-bottom: 1px solid darken(@popover-title-bg, 5%);\n border-radius: (@border-radius-large - 1) (@border-radius-large - 1) 0 0;\n}\n\n.popover-content {\n padding: 9px 14px;\n}\n\n// Arrows\n//\n// .arrow is outer, .arrow:after is inner\n\n.popover > .arrow {\n &,\n &:after {\n position: absolute;\n display: block;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n }\n}\n.popover > .arrow {\n border-width: @popover-arrow-outer-width;\n}\n.popover > .arrow:after {\n border-width: @popover-arrow-width;\n content: \"\";\n}\n\n.popover {\n &.top > .arrow {\n left: 50%;\n margin-left: -@popover-arrow-outer-width;\n border-bottom-width: 0;\n border-top-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-top-color: @popover-arrow-outer-color;\n bottom: -@popover-arrow-outer-width;\n &:after {\n content: \" \";\n bottom: 1px;\n margin-left: -@popover-arrow-width;\n border-bottom-width: 0;\n border-top-color: @popover-arrow-color;\n }\n }\n &.right > .arrow {\n top: 50%;\n left: -@popover-arrow-outer-width;\n margin-top: -@popover-arrow-outer-width;\n border-left-width: 0;\n border-right-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-right-color: @popover-arrow-outer-color;\n &:after {\n content: \" \";\n left: 1px;\n bottom: -@popover-arrow-width;\n border-left-width: 0;\n border-right-color: @popover-arrow-color;\n }\n }\n &.bottom > .arrow {\n left: 50%;\n margin-left: -@popover-arrow-outer-width;\n border-top-width: 0;\n border-bottom-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-bottom-color: @popover-arrow-outer-color;\n top: -@popover-arrow-outer-width;\n &:after {\n content: \" \";\n top: 1px;\n margin-left: -@popover-arrow-width;\n border-top-width: 0;\n border-bottom-color: @popover-arrow-color;\n }\n }\n\n &.left > .arrow {\n top: 50%;\n right: -@popover-arrow-outer-width;\n margin-top: -@popover-arrow-outer-width;\n border-right-width: 0;\n border-left-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-left-color: @popover-arrow-outer-color;\n &:after {\n content: \" \";\n right: 1px;\n border-right-width: 0;\n border-left-color: @popover-arrow-color;\n bottom: -@popover-arrow-width;\n }\n }\n}\n","//\n// Carousel\n// --------------------------------------------------\n\n\n// Wrapper for the slide container and indicators\n.carousel {\n position: relative;\n}\n\n.carousel-inner {\n position: relative;\n overflow: hidden;\n width: 100%;\n\n > .item {\n display: none;\n position: relative;\n .transition(.6s ease-in-out left);\n\n // Account for jankitude on images\n > img,\n > a > img {\n &:extend(.img-responsive);\n line-height: 1;\n }\n\n // WebKit CSS3 transforms for supported devices\n @media all and (transform-3d), (-webkit-transform-3d) {\n .transition-transform(~'0.6s ease-in-out');\n .backface-visibility(~'hidden');\n .perspective(1000);\n\n &.next,\n &.active.right {\n .translate3d(100%, 0, 0);\n left: 0;\n }\n &.prev,\n &.active.left {\n .translate3d(-100%, 0, 0);\n left: 0;\n }\n &.next.left,\n &.prev.right,\n &.active {\n .translate3d(0, 0, 0);\n left: 0;\n }\n }\n }\n\n > .active,\n > .next,\n > .prev {\n display: block;\n }\n\n > .active {\n left: 0;\n }\n\n > .next,\n > .prev {\n position: absolute;\n top: 0;\n width: 100%;\n }\n\n > .next {\n left: 100%;\n }\n > .prev {\n left: -100%;\n }\n > .next.left,\n > .prev.right {\n left: 0;\n }\n\n > .active.left {\n left: -100%;\n }\n > .active.right {\n left: 100%;\n }\n\n}\n\n// Left/right controls for nav\n// ---------------------------\n\n.carousel-control {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n width: @carousel-control-width;\n .opacity(@carousel-control-opacity);\n font-size: @carousel-control-font-size;\n color: @carousel-control-color;\n text-align: center;\n text-shadow: @carousel-text-shadow;\n // We can't have this transition here because WebKit cancels the carousel\n // animation if you trip this while in the middle of another animation.\n\n // Set gradients for backgrounds\n &.left {\n #gradient > .horizontal(@start-color: rgba(0,0,0,.5); @end-color: rgba(0,0,0,.0001));\n }\n &.right {\n left: auto;\n right: 0;\n #gradient > .horizontal(@start-color: rgba(0,0,0,.0001); @end-color: rgba(0,0,0,.5));\n }\n\n // Hover/focus state\n &:hover,\n &:focus {\n outline: 0;\n color: @carousel-control-color;\n text-decoration: none;\n .opacity(.9);\n }\n\n // Toggles\n .icon-prev,\n .icon-next,\n .glyphicon-chevron-left,\n .glyphicon-chevron-right {\n position: absolute;\n top: 50%;\n z-index: 5;\n display: inline-block;\n }\n .icon-prev,\n .glyphicon-chevron-left {\n left: 50%;\n margin-left: -10px;\n }\n .icon-next,\n .glyphicon-chevron-right {\n right: 50%;\n margin-right: -10px;\n }\n .icon-prev,\n .icon-next {\n width: 20px;\n height: 20px;\n margin-top: -10px;\n line-height: 1;\n font-family: serif;\n }\n\n\n .icon-prev {\n &:before {\n content: '\\2039';// SINGLE LEFT-POINTING ANGLE QUOTATION MARK (U+2039)\n }\n }\n .icon-next {\n &:before {\n content: '\\203a';// SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (U+203A)\n }\n }\n}\n\n// Optional indicator pips\n//\n// Add an unordered list with the following class and add a list item for each\n// slide your carousel holds.\n\n.carousel-indicators {\n position: absolute;\n bottom: 10px;\n left: 50%;\n z-index: 15;\n width: 60%;\n margin-left: -30%;\n padding-left: 0;\n list-style: none;\n text-align: center;\n\n li {\n display: inline-block;\n width: 10px;\n height: 10px;\n margin: 1px;\n text-indent: -999px;\n border: 1px solid @carousel-indicator-border-color;\n border-radius: 10px;\n cursor: pointer;\n\n // IE8-9 hack for event handling\n //\n // Internet Explorer 8-9 does not support clicks on elements without a set\n // `background-color`. We cannot use `filter` since that's not viewed as a\n // background color by the browser. Thus, a hack is needed.\n // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Internet_Explorer\n //\n // For IE8, we set solid black as it doesn't support `rgba()`. For IE9, we\n // set alpha transparency for the best results possible.\n background-color: #000 \\9; // IE8\n background-color: rgba(0,0,0,0); // IE9\n }\n .active {\n margin: 0;\n width: 12px;\n height: 12px;\n background-color: @carousel-indicator-active-bg;\n }\n}\n\n// Optional captions\n// -----------------------------\n// Hidden by default for smaller viewports\n.carousel-caption {\n position: absolute;\n left: 15%;\n right: 15%;\n bottom: 20px;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: @carousel-caption-color;\n text-align: center;\n text-shadow: @carousel-text-shadow;\n & .btn {\n text-shadow: none; // No shadow for button elements in carousel-caption\n }\n}\n\n\n// Scale up controls for tablets and up\n@media screen and (min-width: @screen-sm-min) {\n\n // Scale up the controls a smidge\n .carousel-control {\n .glyphicon-chevron-left,\n .glyphicon-chevron-right,\n .icon-prev,\n .icon-next {\n width: 30px;\n height: 30px;\n margin-top: -15px;\n font-size: 30px;\n }\n .glyphicon-chevron-left,\n .icon-prev {\n margin-left: -15px;\n }\n .glyphicon-chevron-right,\n .icon-next {\n margin-right: -15px;\n }\n }\n\n // Show and left align the captions\n .carousel-caption {\n left: 20%;\n right: 20%;\n padding-bottom: 30px;\n }\n\n // Move up the indicators\n .carousel-indicators {\n bottom: 20px;\n }\n}\n","// Clearfix\n//\n// For modern browsers\n// 1. The space content is one way to avoid an Opera bug when the\n// contenteditable attribute is included anywhere else in the document.\n// Otherwise it causes space to appear at the top and bottom of elements\n// that are clearfixed.\n// 2. The use of `table` rather than `block` is only necessary if using\n// `:before` to contain the top-margins of child elements.\n//\n// Source: http://nicolasgallagher.com/micro-clearfix-hack/\n\n.clearfix() {\n &:before,\n &:after {\n content: \" \"; // 1\n display: table; // 2\n }\n &:after {\n clear: both;\n }\n}\n","// Center-align a block level element\n\n.center-block() {\n display: block;\n margin-left: auto;\n margin-right: auto;\n}\n","// CSS image replacement\n//\n// Heads up! v3 launched with with only `.hide-text()`, but per our pattern for\n// mixins being reused as classes with the same name, this doesn't hold up. As\n// of v3.0.1 we have added `.text-hide()` and deprecated `.hide-text()`.\n//\n// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757\n\n// Deprecated as of v3.0.1 (will be removed in v4)\n.hide-text() {\n font: ~\"0/0\" a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n\n// New mixin to use as of v3.0.1\n.text-hide() {\n .hide-text();\n}\n","//\n// Responsive: Utility classes\n// --------------------------------------------------\n\n\n// IE10 in Windows (Phone) 8\n//\n// Support for responsive views via media queries is kind of borked in IE10, for\n// Surface/desktop in split view and for Windows Phone 8. This particular fix\n// must be accompanied by a snippet of JavaScript to sniff the user agent and\n// apply some conditional CSS to *only* the Surface/desktop Windows 8. Look at\n// our Getting Started page for more information on this bug.\n//\n// For more information, see the following:\n//\n// Issue: https://github.com/twbs/bootstrap/issues/10497\n// Docs: http://getbootstrap.com/getting-started/#support-ie10-width\n// Source: http://timkadlec.com/2013/01/windows-phone-8-and-device-width/\n// Source: http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/\n\n@-ms-viewport {\n width: device-width;\n}\n\n\n// Visibility utilities\n// Note: Deprecated .visible-xs, .visible-sm, .visible-md, and .visible-lg as of v3.2.0\n.visible-xs,\n.visible-sm,\n.visible-md,\n.visible-lg {\n .responsive-invisibility();\n}\n\n.visible-xs-block,\n.visible-xs-inline,\n.visible-xs-inline-block,\n.visible-sm-block,\n.visible-sm-inline,\n.visible-sm-inline-block,\n.visible-md-block,\n.visible-md-inline,\n.visible-md-inline-block,\n.visible-lg-block,\n.visible-lg-inline,\n.visible-lg-inline-block {\n display: none !important;\n}\n\n.visible-xs {\n @media (max-width: @screen-xs-max) {\n .responsive-visibility();\n }\n}\n.visible-xs-block {\n @media (max-width: @screen-xs-max) {\n display: block !important;\n }\n}\n.visible-xs-inline {\n @media (max-width: @screen-xs-max) {\n display: inline !important;\n }\n}\n.visible-xs-inline-block {\n @media (max-width: @screen-xs-max) {\n display: inline-block !important;\n }\n}\n\n.visible-sm {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n .responsive-visibility();\n }\n}\n.visible-sm-block {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n display: block !important;\n }\n}\n.visible-sm-inline {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n display: inline !important;\n }\n}\n.visible-sm-inline-block {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n display: inline-block !important;\n }\n}\n\n.visible-md {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n .responsive-visibility();\n }\n}\n.visible-md-block {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n display: block !important;\n }\n}\n.visible-md-inline {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n display: inline !important;\n }\n}\n.visible-md-inline-block {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n display: inline-block !important;\n }\n}\n\n.visible-lg {\n @media (min-width: @screen-lg-min) {\n .responsive-visibility();\n }\n}\n.visible-lg-block {\n @media (min-width: @screen-lg-min) {\n display: block !important;\n }\n}\n.visible-lg-inline {\n @media (min-width: @screen-lg-min) {\n display: inline !important;\n }\n}\n.visible-lg-inline-block {\n @media (min-width: @screen-lg-min) {\n display: inline-block !important;\n }\n}\n\n.hidden-xs {\n @media (max-width: @screen-xs-max) {\n .responsive-invisibility();\n }\n}\n.hidden-sm {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n .responsive-invisibility();\n }\n}\n.hidden-md {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n .responsive-invisibility();\n }\n}\n.hidden-lg {\n @media (min-width: @screen-lg-min) {\n .responsive-invisibility();\n }\n}\n\n\n// Print utilities\n//\n// Media queries are placed on the inside to be mixin-friendly.\n\n// Note: Deprecated .visible-print as of v3.2.0\n.visible-print {\n .responsive-invisibility();\n\n @media print {\n .responsive-visibility();\n }\n}\n.visible-print-block {\n display: none !important;\n\n @media print {\n display: block !important;\n }\n}\n.visible-print-inline {\n display: none !important;\n\n @media print {\n display: inline !important;\n }\n}\n.visible-print-inline-block {\n display: none !important;\n\n @media print {\n display: inline-block !important;\n }\n}\n\n.hidden-print {\n @media print {\n .responsive-invisibility();\n }\n}\n","// Responsive utilities\n\n//\n// More easily include all the states for responsive-utilities.less.\n.responsive-visibility() {\n display: block !important;\n table& { display: table; }\n tr& { display: table-row !important; }\n th&,\n td& { display: table-cell !important; }\n}\n\n.responsive-invisibility() {\n display: none !important;\n}\n"]}
\ No newline at end of file
diff --git a/http2/doc/api/static-assets/favicon.png b/http2/doc/api/static-assets/favicon.png
new file mode 100644
index 0000000..7d3901d
--- /dev/null
+++ b/http2/doc/api/static-assets/favicon.png
Binary files differ
diff --git a/http2/doc/api/static-assets/play_button.svg b/http2/doc/api/static-assets/play_button.svg
new file mode 100644
index 0000000..c39a2f4
--- /dev/null
+++ b/http2/doc/api/static-assets/play_button.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="68" height="68" viewBox="0 0 17.992 17.992"><path d="M17.992 8.996A8.996 8.996 0 1 0 0 8.996a8.996 8.996 0 0 0 17.992 0m-2.23 0l-9.895 5.713V3.282l9.896 5.714h2.229z" fill-opacity=".198"/><path d="M15.763 8.996l-9.896 5.713V3.283z" fill="#7d7d7d" fill-opacity=".821"/></svg>
\ No newline at end of file
diff --git a/http2/doc/api/static-assets/readme.md b/http2/doc/api/static-assets/readme.md
new file mode 100644
index 0000000..287f566
--- /dev/null
+++ b/http2/doc/api/static-assets/readme.md
@@ -0,0 +1,19 @@
+# highlight.js
+
+Generated from https://highlightjs.org/download/ on 2017-08-30
+
+Included languages:
+
+* bash
+* css
+* dart
+* java
+* javascript
+* json
+* markdown
+* objectivec
+* ruby - dragged in by `yaml` - 🙄
+* shell
+* swift
+* xml - includes html
+* yaml
diff --git a/http2/example/display_headers.dart b/http2/example/display_headers.dart
new file mode 100644
index 0000000..c1faa4d
--- /dev/null
+++ b/http2/example/display_headers.dart
@@ -0,0 +1,63 @@
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:http2/transport.dart';
+
+main(List<String> args) async {
+ if (args == null || args.length != 1) {
+ print('Usage: dart display_headers.dart <HTTPS_URI>');
+ exit(1);
+ }
+
+ var uriArg = args[0];
+
+ if (!uriArg.startsWith('https://')) {
+ print('URI must start with https://');
+ exit(1);
+ }
+
+ var uri = Uri.parse(uriArg);
+
+ var socket = await connect(uri);
+
+ // The default client settings will disable server pushes. We
+ // therefore do not need to deal with [stream.peerPushes].
+ var transport = new ClientTransportConnection.viaSocket(socket);
+
+ var headers = [
+ new Header.ascii(':method', 'GET'),
+ new Header.ascii(':path', uri.path),
+ new Header.ascii(':scheme', uri.scheme),
+ new Header.ascii(':authority', uri.host),
+ ];
+
+ var stream = transport.makeRequest(headers, endStream: true);
+ await for (var message in stream.incomingMessages) {
+ if (message is HeadersStreamMessage) {
+ for (var header in message.headers) {
+ var name = utf8.decode(header.name);
+ var value = utf8.decode(header.value);
+ print('$name: $value');
+ }
+ } else if (message is DataStreamMessage) {
+ // Use [message.bytes] (but respect 'content-encoding' header)
+ }
+ }
+ await transport.finish();
+}
+
+Future<Socket> connect(Uri uri) async {
+ bool useSSL = uri.scheme == 'https';
+ if (useSSL) {
+ var secureSocket = await SecureSocket.connect(uri.host, uri.port,
+ supportedProtocols: ['h2']);
+ if (secureSocket.selectedProtocol != 'h2') {
+ throw new Exception("Failed to negogiate http/2 via alpn. Maybe server "
+ "doesn't support http/2.");
+ }
+ return secureSocket;
+ } else {
+ return await Socket.connect(uri.host, uri.port);
+ }
+}
diff --git a/http2/experimental/server.dart b/http2/experimental/server.dart
new file mode 100644
index 0000000..6637cc2
--- /dev/null
+++ b/http2/experimental/server.dart
@@ -0,0 +1,167 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library http2.experimental.server;
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import 'package:http2/src/testing/debug.dart' hide print;
+import 'package:http2/transport.dart';
+
+const bool DEBUGGING = false;
+
+const String HOSTNAME = 'localhost';
+const int PORT = 7777;
+
+main() async {
+ String localFile(String path) => Platform.script.resolve(path).toFilePath();
+
+ var context = new SecurityContext()
+ ..usePrivateKey(localFile('server_key.pem'), password: 'dartdart')
+ ..useCertificateChain(localFile('server_chain.pem'))
+ ..setAlpnProtocols(['h2'], true);
+
+ var server = await SecureServerSocket.bind(HOSTNAME, PORT, context);
+ print('HTTP/2 server listening on https://$HOSTNAME:$PORT');
+
+ runZoned(() {
+ server.listen(handleClient);
+ }, onError: (e, s) {
+ print("Unexpected error: $e");
+ print("Unexpected error - stack: $s");
+ });
+}
+
+handleClient(SecureSocket socket) {
+ dumpInfo('main', 'Got new https client');
+
+ var connection;
+ if (DEBUGGING) {
+ connection = debugPrintingConnection(socket);
+ } else {
+ connection = new ServerTransportConnection.viaSocket(socket);
+ }
+
+ connection.incomingStreams.listen((ServerTransportStream stream) async {
+ dumpInfo('main', 'Got new HTTP/2 stream with id: ${stream.id}');
+
+ String path;
+ stream.incomingMessages.listen((StreamMessage msg) async {
+ dumpInfo('${stream.id}', 'Got new incoming message');
+ if (msg is HeadersStreamMessage) {
+ dumpHeaders('${stream.id}', msg.headers);
+ if (path == null) {
+ path = pathFromHeaders(msg.headers);
+ if (path == null) throw new Exception('no path given');
+
+ if (path == '/') {
+ sendHtml(stream);
+ } else if (['/iframe', '/iframe2'].contains(path)) {
+ sendIFrameHtml(stream, path);
+ } else {
+ send404(stream, path);
+ }
+ }
+ } else if (msg is DataStreamMessage) {
+ dumpData('${stream.id}', msg.bytes);
+ }
+ });
+ });
+}
+
+void dumpHeaders(String prefix, List<Header> headers) {
+ for (int i = 0; i < headers.length; i++) {
+ String key = ascii.decode(headers[i].name);
+ String value = ascii.decode(headers[i].value);
+ print('[$prefix] $key: $value');
+ }
+}
+
+String pathFromHeaders(List<Header> headers) {
+ for (int i = 0; i < headers.length; i++) {
+ if (ascii.decode(headers[i].name) == ':path') {
+ return ascii.decode(headers[i].value);
+ }
+ }
+ throw new Exception('Expected a :path header, but did not find one.');
+}
+
+void dumpData(String prefix, List<int> data) {
+ print('[$prefix] Got ${data.length} bytes.');
+}
+
+void dumpInfo(String prefix, String msg) {
+ print('[$prefix] $msg');
+}
+
+Future sendHtml(ServerTransportStream stream) async {
+ push(stream, '/iframe', sendIFrameHtml);
+ push(stream, '/iframe2', sendIFrameHtml);
+ push(stream, '/favicon.ico', send404);
+
+ stream.sendHeaders([
+ new Header.ascii(':status', '200'),
+ new Header.ascii('content-type', 'text/html; charset=utf-8'),
+ ]);
+ stream.sendData(ascii.encode('''
+<html>
+ <head><title>hello</title></head>
+ <body>
+ <h1> head </h1>
+ first <br />
+ <iframe src='/iframe' with="100" height="100"></iframe> <br />
+ second <br />
+ <iframe src='/iframe2' with="100" height="100"></iframe> <br />
+ </body>
+</html>
+'''));
+ return stream.outgoingMessages.close();
+}
+
+Future push(ServerTransportStream stream, String path,
+ Future sendResponse(TransportStream stream, String path)) async {
+ var requestHeaders = [
+ new Header.ascii(':authority', '$HOSTNAME:$PORT'),
+ new Header.ascii(':method', 'GET'),
+ new Header.ascii(':path', path),
+ new Header.ascii(':scheme', 'https'),
+ ];
+
+ var pushStream = stream.push(requestHeaders);
+ await sendResponse(pushStream, path);
+}
+
+Future sendIFrameHtml(TransportStream stream, String path) async {
+ stream.sendHeaders([
+ new Header.ascii(':status', '200'),
+ new Header.ascii('content-type', 'text/html; charset=utf-8'),
+ ]);
+ stream.sendData(ascii.encode('''
+<html>
+ <head><title>Content for '$path' inside an IFrame.</title></head>
+ <body>
+ <h2>Content for '$path' inside an IFrame.</h2>
+ </body>
+</html>
+'''));
+ await stream.outgoingMessages.close();
+}
+
+Future send404(TransportStream stream, String path) async {
+ stream.sendHeaders([
+ new Header.ascii(':status', '404'),
+ new Header.ascii('content-type', 'text/html; charset=utf-8'),
+ ]);
+ stream.sendData(ascii.encode('''
+<html>
+ <head><title>Path '$path' was not found on this server.</title></head>
+ <body>
+ <h1>Path '$path' was not found on this server.</h1>
+ </body>
+</html>
+'''));
+ return stream.outgoingMessages.close();
+}
diff --git a/http2/experimental/server_chain.pem b/http2/experimental/server_chain.pem
new file mode 100644
index 0000000..4163fe7
--- /dev/null
+++ b/http2/experimental/server_chain.pem
@@ -0,0 +1,57 @@
+-----BEGIN CERTIFICATE-----
+MIIDKTCCAhGgAwIBAgIJAOWmjTS+OnTEMA0GCSqGSIb3DQEBCwUAMBcxFTATBgNV
+BAMMDGludGVybWVkaWF0ZTAeFw0xNTA1MTgwOTAwNDBaFw0yMzA4MDQwOTAwNDBa
+MBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBALlcwQJuzd+xH8QFgfJSn5tRlvhkldSX98cE7NiA602NBbnAVyUrkRXq
+Ni75lgt0kwjYfA9z674m8WSVbgpLPintPCla9CYky1TH0keIs8Rz6cGWHryWEHiu
+EDuljQynu2b3sAFuHu9nfWurbJwZnFakBKpdQ9m4EyOZCHC/jHYY7HacKSXg1Cki
+we2ca0BWDrcqy8kLy0dZ5oC6IZG8O8drAK8f3f44CRYw59D3sOKBrKXaabpvyEcb
+N7Wk2HDBVwHpUJo1reVwtbM8dhqQayYSD8oXnGpP3RQNu/e2rzlXRyq/BfcDY1JI
+7TbC4t/7/N4EcPSpGsTcSOC9A7FpzvECAwEAAaN7MHkwCQYDVR0TBAIwADAsBglg
+hkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0O
+BBYEFCnwiEMMFZh7NhCr+qA8K0w4Q+AOMB8GA1UdIwQYMBaAFB0h1Evsaw2vfrmS
+YuoCTmC4EE6ZMA0GCSqGSIb3DQEBCwUAA4IBAQAcFmHMaXRxyoNaeOowQ6iQWoZd
+AUbvG7SHr7I6Pi2aqdqofsKWts7Ytm5WsS0M2nN+sW504houu0iCPeJJX8RQw2q4
+CCcNOs9IXk+2uMzlpocHpv+yYoUiD5DxgWh7eghQMLyMpf8FX3Gy4VazeuXznHOM
+4gE4L417xkDzYOzqVTp0FTyAPUv6G2euhNCD6TMru9REcRhYul+K9kocjA5tt2KG
+MH6y28LXbLyq4YJUxSUU9gY/xlnbbZS48KDqEcdYC9zjW9nQ0qS+XQuQuFIcwjJ5
+V4kAUYxDu6FoTpyQjgsrmBbZlKNxH7Nj4NDlcdJhp/zeSKHqWa5hSWjjKIxp
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDAjCCAeqgAwIBAgIJAOWmjTS+OnTDMA0GCSqGSIb3DQEBCwUAMBgxFjAUBgNV
+BAMMDXJvb3RhdXRob3JpdHkwHhcNMTUwNTE4MDkwMDQwWhcNMjMwODA0MDkwMDQw
+WjAXMRUwEwYDVQQDDAxpbnRlcm1lZGlhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IB
+DwAwggEKAoIBAQDSrAO1CoPvUllgLOzDm5nG0skDF7vh1DUgAIDVGz0ecD0JFbQx
+EF79pju/6MbtpTW2FYvRp11t/G7rGtX923ybOHY/1MNFQrdIvPlO1VV7IGKjoMwP
+DNeb0fIGjHoE9QxaDxR8NX8xQbItpsw+TUtRfc9SLkR+jaYJfVRoM21BOncZbSHE
+YKiZlEbpecB/+EtwVpgvl+8mPD5U07Fi4fp/lza3WXInXQPyiTVllIEJCt4PKmlu
+MocNaJOW38bysL7i0PzDpVZtOxLHOTaW68yF3FckIHNCaA7k1ABEEEegjFMmIao7
+B9w7A0jvr4jZVvNmui5Djjn+oJxwEVVgyf8LAgMBAAGjUDBOMB0GA1UdDgQWBBQd
+IdRL7GsNr365kmLqAk5guBBOmTAfBgNVHSMEGDAWgBRk81s9d0ZbiZhh44KckwPb
+oTc0XzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBZQTK0plfdB5PC
+cC5icut4EmrByJa1RbU7ayuEE70e7hla6KVmVjVdCBGltI4jBYwfhKbRItHiAJ/8
+x+XZKBG8DLPFuDb7lAa1ObhAYF7YThUFPQYaBhfzKcWrdmWDBFpvNv6E0Mm364dZ
+e7Yxmbe5S4agkYPoxEzgEYmcUk9jbjdR6eTbs8laG169ljrECXfEU9RiAcqz5iSX
+NLSewqB47hn3B9qgKcQn+PsgO2j7M+rfklhNgeGJeWmy7j6clSOuCsIjWHU0RLQ4
+0W3SB/rpEAJ7fgQbYUPTIUNALSOWi/o1tDX2mXPRjBoxqAv7I+vYk1lZPmSzkyRh
+FKvRDxsW
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDAzCCAeugAwIBAgIJAJ0MomS4Ck+8MA0GCSqGSIb3DQEBCwUAMBgxFjAUBgNV
+BAMMDXJvb3RhdXRob3JpdHkwHhcNMTUwNTE4MDkwMDQwWhcNMjMwODA0MDkwMDQw
+WjAYMRYwFAYDVQQDDA1yb290YXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEAts1ijtBV92S2cOvpUMOSTp9c6A34nIGr0T5Nhz6XiqRVT+gv
+dQgmkdKJQjbvR60y6jzltYFsI2MpGVXY8h/oAL81D/k7PDB2aREgyBfTPAhBHyGw
+siR+2xYt5b/Zs99q5RdRqQNzNpLPJriIKvUsRyQWy1UiG2s7pRXQeA8qB0XtJdCj
+kFIi+G2bDsaffspGeDOCqt7t+yqvRXfSES0c/l7DIHaiMbbp4//ZNML3RNgAjPz2
+hCezZ+wOYajOIyoSPK8IgICrhYFYxvgWxwbLDBEfC5B3jOQsySe10GoRAKZz1gBV
+DmgReu81tYJmdgkc9zknnQtIFdA0ex+GvZlfWQIDAQABo1AwTjAdBgNVHQ4EFgQU
+ZPNbPXdGW4mYYeOCnJMD26E3NF8wHwYDVR0jBBgwFoAUZPNbPXdGW4mYYeOCnJMD
+26E3NF8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEATzkZ97K777uZ
+lQcduNX3ey4IbCiEzFA2zO5Blj+ilfIwNbZXNOgm/lqNvVGDYs6J1apJJe30vL3X
+J+t2zsZWzzQzb9uIU37zYemt6m0fHrSrx/iy5lGNqt3HMfqEcOqSCOIK3PCTMz2/
+uyGe1iw33PVeWsm1JUybQ9IrU/huJjbgOHU4wab+8SJCM49ipArp68Fr6j4lcEaE
+4rfRg1ZsvxiOyUB3qPn6wyL/JB8kOJ+QCBe498376eaem8AEFk0kQRh6hDaWtq/k
+t6IIXQLjx+EBDVP/veK0UnVhKRP8YTOoV8ZiG1NcdlJmX/Uk7iAfevP7CkBfSN8W
+r6AL284qtw==
+-----END CERTIFICATE-----
diff --git a/http2/experimental/server_key.pem b/http2/experimental/server_key.pem
new file mode 100644
index 0000000..1fd2324
--- /dev/null
+++ b/http2/experimental/server_key.pem
@@ -0,0 +1,29 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIE5DAcBgoqhkiG9w0BDAEBMA4ECL7L6rj6uEHGAgIIAASCBMLbucyfqAkgCbhP
+xNSHYllPMAv/dsIjtnsBwepCXPGkCBCuOAw/2FaCHjN9hBqL5V7fkrKeaemhm2YE
+ycPtlHJYPDf3kEkyMjdZ9rIY6kePGfQizs2uJPcXj4YPyQ4HsfVXpOicKfQrouf5
+Mze9bGzeMN065q3iP4dYUMwHAyZYteXCsanQNHlqvsWli0W+H8St8fdsXefZhnv1
+qVatKWdNdWQ9t5MuljgNU2Vv56sHKEYXI0yLxk2QUMk8KlJfnmt8foYUsnPUXHmc
+gIjLKwwVkpdololnEHSNu0cEOUPowjgJru+uMpn7vdNl7TPEQ9jbEgdNg4JwoYzU
+0nao8WzjaSp7kzvZz0VFwKnk5AjstGvvuAWckADdq23QElbn/mF7AG1m/TBpYxzF
+gTt37UdndS/AcvVznWVVrRP5iTSIawdIwvqI4s7rqsoE0GCcak+RhchgAz2gWKkS
+oODUo0JL6pPVbJ3l4ebbaO6c99nDVc8dViPtc1EkStJEJ2O4kI4xgLSCr4Y9ahKn
+oAaoSkX7Xxq3aQm+BzqSpLjdGL8atsqR/YVOIHYIl3gThvP0NfZGx1xHyvO5mCdZ
+kHxSA7tKWxauZ3eQ2clbnzeRsl4El0WMHy/5K1ovene4v7sunmoXVtghBC8hK6eh
+zMO9orex2PNQ/VQC7HCvtytunOVx1lkSBoNo7hR70igg6rW9H7UyoAoBOwMpT1xa
+J6V62nqruTKOqFNfur7aHJGpHGtDb5/ickHeYCyPTvmGp67u4wChzKReeg02oECe
+d1E5FKAcIa8s9TVOB6Z+HvTRNQZu2PsI6TJnjQRowvY9DAHiWTlJZBBY/pko3hxX
+TsIeybpvRdEHpDWv86/iqtw1hv9CUxS/8ZTWUgBo+osShHW79FeDASr9FC4/Zn76
+ZDERTgV4YWlW/klVWcG2lFo7jix+OPXAB+ZQavLhlN1xdWBcIz1AUWjAM4hdPylW
+HCX4PB9CQIPl2E7F+Y2p6nMcMWSJVBi5UIH7E9LfaBguXSzMmTk2Fw5p1aOQ6wfN
+goVAMVwi8ppAVs741PfHdZ295xMmK/1LCxz5DeAdD/tsA/SYfT753GotioDuC7im
+EyJ5JyvTr5I6RFFBuqt3NlUb3Hp16wP3B2x9DZiB6jxr0l341/NHgsyeBXkuIy9j
+ON2mvpBPCJhS8kgWo3G0UyyKnx64tcgpGuSvZhGwPz843B6AbYyE6pMRfSWRMkMS
+YZYa+VNKhR4ixdj07ocFZEWLVjCH7kxkE8JZXKt8jKYmkWd0lS1QVjgaKlO6lRa3
+q6SPJkhW6pvqobvcqVNXwi1XuzpZeEbuh0B7OTekFTTxx5g9XeDl56M8SVQ1KEhT
+Q1t7H2Nba18WCB7cf+6PN0F0K0Jz1Kq7ZWaqEI/grX1m4RQuvNF5807sB/QKMO/Z
+Gz3NXvHg5xTJRd/567lxPGkor0cE7qD1EZfmJ2HrBYXQ91bhgA7LToBuMZo6ZRXH
+QfsanjbP4FPLMiGdQigLjj3A35L/f4sQOOVac/sRaFnm7pzcxsMvyVU/YtvGcjYE
+xaOOVnamg661Wo0wksXoDjeSz/JIyyKO3Gwp1FSm2wGLjjy/Ehmqcqy8rvHuf07w
+AUukhVtTNn4=
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/http2/lib/http2.dart b/http2/lib/http2.dart
new file mode 100644
index 0000000..3f1ed78
--- /dev/null
+++ b/http2/lib/http2.dart
@@ -0,0 +1,48 @@
+// Copyright (c) 2018, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// This library provides an http/2 interface on top of a bidirectional stream
+/// of bytes.
+///
+/// The client and server sides can be created via [ClientTransportStream] and
+/// [ServerTransportStream] respectively. Both sides can be configured via
+/// settings (see [ClientSettings] and [ServerSettings]). The settings will be
+/// communicated to the remote peer (if necessary) and will be valid during the
+/// entire lifetime of the connection.
+///
+/// A http/2 transport allows a client to open a bidirectional stream (see
+/// [ClientTransportConnection.makeRequest]) and a server can open (or push) a
+/// unidirectional stream to the client via [ServerTransportStream.push].
+///
+/// In both cases (unidirectional and bidirectional stream), one can send
+/// headers and data to the other side (via [HeadersStreamMessage] and
+/// [DataStreamMessage]). These messages are ordered and will arrive in the same
+/// order as they were sent (data messages may be split up into multiple smaller
+/// chunks or might be combined).
+///
+/// In the most common case, each direction will send one [HeadersStreamMessage]
+/// followed by zero or more [DataStreamMessage]s.
+///
+/// Establishing a bidirectional stream of bytes to a server is up to the user
+/// of this library. There are 3 common ways to achive this
+///
+/// * connect to a server via SSL and use the ALPN (SSL) protocol extension
+/// to negotiate with the server to speak http/2 (the ALPN protocol
+/// identifier for http/2 is `h2`)
+///
+/// * have prior knowledge about the server - i.e. know ahead of time that
+/// the server will speak http/2 via an unencrypted tcp connection
+///
+/// * use a http/1.1 connection and upgrade it to http/2
+///
+/// The first way is the most common way and can be done in Dart by using
+/// `dart:io`s secure socket implementation (by using a `SecurityContext` and
+/// including 'h2' in the list of protocols used for ALPN).
+///
+/// A simple example on how to connect to a http/2 capable server and
+/// requesting a resource is available at https://github.com/dart-lang/http2/blob/master/example/display_headers.dart.
+library http2.http2;
+
+import 'transport.dart';
+export 'transport.dart';
diff --git a/http2/lib/multiprotocol_server.dart b/http2/lib/multiprotocol_server.dart
new file mode 100644
index 0000000..6fd168e
--- /dev/null
+++ b/http2/lib/multiprotocol_server.dart
@@ -0,0 +1,125 @@
+// Copyright (c) 2016 the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library http2.multiprotocol_server;
+
+import 'dart:async';
+import 'dart:io';
+
+import 'src/artificial_server_socket.dart';
+import 'transport.dart' as http2;
+
+/// Handles protocol negotiation with HTTP/1.1 and HTTP/2 clients.
+///
+/// Given a (host, port) pair and a [SecurityContext], [MultiProtocolHttpServer]
+/// will negotiate with the client whether HTTP/1.1 or HTTP/2 should be spoken.
+///
+/// The user must supply 2 callback functions to [startServing], which:
+/// * one handles HTTP/1.1 clients (called with a [HttpRequest])
+/// * one handles HTTP/2 clients (called with a [http2.ServerTransportStream])
+class MultiProtocolHttpServer {
+ final SecureServerSocket _serverSocket;
+ final http2.ServerSettings _settings;
+
+ _ServerSocketController _http11Controller;
+ HttpServer _http11Server;
+
+ StreamController<http2.ServerTransportStream> _http2Controller;
+ Stream<http2.ServerTransportStream> _http2Server;
+ final _http2Connections = new Set<http2.ServerTransportConnection>();
+
+ MultiProtocolHttpServer._(this._serverSocket, this._settings) {
+ _http11Controller =
+ new _ServerSocketController(_serverSocket.address, _serverSocket.port);
+ _http11Server = new HttpServer.listenOn(_http11Controller.stream);
+
+ _http2Controller = new StreamController();
+ _http2Server = _http2Controller.stream;
+ }
+
+ /// Binds a new [SecureServerSocket] with a security [context] at [port] and
+ /// [address] (see [SecureServerSocket.bind] for a description of supported
+ /// types for [address]).
+ ///
+ /// Optionally [settings] can be supplied which will be used for HTTP/2
+ /// clients.
+ ///
+ /// See also [startServing].
+ static Future<MultiProtocolHttpServer> bind(
+ address, int port, SecurityContext context,
+ {http2.ServerSettings settings}) async {
+ context.setAlpnProtocols(['h2', 'h2-14', 'http/1.1'], true);
+ var secureServer = await SecureServerSocket.bind(address, port, context);
+ return new MultiProtocolHttpServer._(secureServer, settings);
+ }
+
+ /// The port this multi-protocol HTTP server runs on.
+ int get port => _serverSocket.port;
+
+ /// The address this multi-protocol HTTP server runs on.
+ InternetAddress get address => _serverSocket.address;
+
+ /// Starts listening for HTTP/1.1 and HTTP/2 clients and calls the given
+ /// callbacks for new clients.
+ ///
+ /// It is expected that [callbackHttp11] and [callbackHttp2] will never throw
+ /// an exception (i.e. these must take care of error handling themselves).
+ void startServing(void callbackHttp11(HttpRequest request),
+ void callbackHttp2(http2.ServerTransportStream stream),
+ {void onError(error, StackTrace stack)}) {
+ // 1. Start listening on the real [SecureServerSocket].
+ _serverSocket.listen((SecureSocket socket) {
+ var protocol = socket.selectedProtocol;
+ if (protocol == null || protocol == 'http/1.1') {
+ _http11Controller.addHttp11Socket(socket);
+ } else if (protocol == 'h2' || protocol == 'h2-14') {
+ var connection = new http2.ServerTransportConnection.viaSocket(socket,
+ settings: _settings);
+ _http2Connections.add(connection);
+ connection.incomingStreams.listen(_http2Controller.add,
+ onError: onError,
+ onDone: () => _http2Connections.remove(connection));
+ } else {
+ socket.destroy();
+ throw new Exception("Unexpected negotiated ALPN protocol: $protocol.");
+ }
+ }, onError: onError);
+
+ // 2. Drain all incoming http/1.1 and http/2 connections and call the
+ // respective handlers.
+ _http11Server.listen(callbackHttp11);
+ _http2Server.listen(callbackHttp2);
+ }
+
+ /// Closes this [MultiProtocolHttpServer].
+ ///
+ /// Completes once everything has been successfully shut down.
+ Future close({bool force: false}) {
+ return _serverSocket.close().whenComplete(() {
+ Future done1 = _http11Server.close(force: force);
+ Future done2 = Future.wait(
+ _http2Connections.map((c) => force ? c.terminate() : c.finish()));
+ return Future.wait([done1, done2]);
+ });
+ }
+}
+
+/// An internal helper class.
+class _ServerSocketController {
+ final InternetAddress address;
+ final int port;
+ final StreamController<Socket> _controller = new StreamController();
+
+ _ServerSocketController(this.address, this.port);
+
+ ArtificialServerSocket get stream {
+ return new ArtificialServerSocket(address, port, _controller.stream);
+ }
+
+ void addHttp11Socket(Socket socket) {
+ _controller.add(socket);
+ }
+
+ Future close() => _controller.close();
+}
diff --git a/http2/lib/src/artificial_server_socket.dart b/http2/lib/src/artificial_server_socket.dart
new file mode 100644
index 0000000..4c5cf07
--- /dev/null
+++ b/http2/lib/src/artificial_server_socket.dart
@@ -0,0 +1,34 @@
+// Copyright (c) 2016 the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library http2.artificial_server_socket;
+
+import 'dart:async';
+import 'dart:io';
+
+/// Custom implementation of the [ServerSocket] interface.
+///
+/// This class can be used to create a [ServerSocket] using [Stream<Socket>] and
+/// a [InternetAddress] and `port` (an example use case is to filter [Socket]s
+/// and keep the [ServerSocket] interface for APIs that expect it,
+/// e.g. `new HttpServer.listenOn()`).
+class ArtificialServerSocket extends StreamView<Socket>
+ implements ServerSocket {
+ ArtificialServerSocket(this.address, this.port, Stream<Socket> stream)
+ : super(stream);
+
+ // ########################################################################
+ // These are the methods of [ServerSocket] in addition to [Stream<Socket>].
+ // ########################################################################
+
+ final InternetAddress address;
+
+ final int port;
+
+ /// Closing of an [ArtificialServerSocket] is not possible and an exception
+ /// will be thrown when calling this method.
+ Future<ServerSocket> close() async {
+ throw new Exception("Did not expect close() to be called.");
+ }
+}
diff --git a/http2/lib/src/async_utils/async_utils.dart b/http2/lib/src/async_utils/async_utils.dart
new file mode 100644
index 0000000..3f6d44a
--- /dev/null
+++ b/http2/lib/src/async_utils/async_utils.dart
@@ -0,0 +1,138 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library http2.src.async_utils.async_utils;
+
+import 'dart:async';
+import 'dart:io';
+
+/// An interface for `StreamSink`-like classes to indicate whether adding data
+/// would be buffered and when the buffer is empty again.
+class BufferIndicator {
+ final StreamController _controller =
+ new StreamController.broadcast(sync: true);
+
+ /// A state variable indicating whether buffereing would occur at the moment.
+ bool _wouldBuffer = true;
+
+ /// Indicates whether calling [BufferedBytesWriter.add] would buffer the data
+ /// if called.
+ ///
+ /// This can be used at a higher level as a way to do custom buffering and
+ /// possibly prioritization.
+ bool get wouldBuffer {
+ return _wouldBuffer;
+ }
+
+ /// Signals that no buffering is happening at the moment.
+ void markUnBuffered() {
+ if (_wouldBuffer) {
+ _wouldBuffer = false;
+ _controller.add(null);
+ }
+ }
+
+ /// Signals that buffering starts to happen.
+ void markBuffered() {
+ _wouldBuffer = true;
+ }
+
+ /// A broadcast stream notifying users that the [BufferedBytesWriter.add]
+ /// method would not buffer the data if called.
+ Stream get bufferEmptyEvents => _controller.stream;
+}
+
+/// Contains a [StreamSink] and a [BufferIndicator] to indicate whether writes
+/// to the sink would cause buffering.
+///
+/// It uses the `pause signal` from the `sink.addStream()` as an indicator
+/// whether the underlying stream cannot handle more data and would buffer.
+class BufferedSink {
+ /// The indicator whether the underlying sink is buffering at the moment.
+ final BufferIndicator bufferIndicator = new BufferIndicator();
+
+ /// A intermediate [StreamController] used to catch pause signals and to
+ /// propagate the change via [bufferIndicator].
+ StreamController<List<int>> _controller;
+
+ /// A future which completes once the sink has been closed.
+ Future _doneFuture;
+
+ BufferedSink(StreamSink<List<int>> dataSink) {
+ bufferIndicator.markBuffered();
+
+ _controller = new StreamController<List<int>>(
+ onListen: () {
+ bufferIndicator.markUnBuffered();
+ },
+ onPause: () {
+ bufferIndicator.markBuffered();
+ },
+ onResume: () {
+ bufferIndicator.markUnBuffered();
+ },
+ onCancel: () {
+ // TODO: We may want to propagate cancel events as errors.
+ // Currently `_doneFuture` will just complete normally if the sink
+ // cancelled.
+ },
+ sync: true);
+ _doneFuture =
+ Future.wait([_controller.stream.pipe(dataSink), dataSink.done]);
+ }
+
+ /// The underlying sink.
+ StreamSink<List<int>> get sink => _controller;
+
+ /// The future which will complete once this sink has been closed.
+ Future get doneFuture => _doneFuture;
+}
+
+/// A small wrapper around [BufferedSink] which writes data in batches.
+class BufferedBytesWriter {
+ /// A buffer which will be used for batching writes.
+ final BytesBuilder _builder = new BytesBuilder(copy: false);
+
+ /// The underlying [BufferedSink].
+ final BufferedSink _bufferedSink;
+
+ BufferedBytesWriter(StreamSink<List<int>> outgoing)
+ : _bufferedSink = new BufferedSink(outgoing);
+
+ /// An indicator whether the underlying sink is buffering at the moment.
+ BufferIndicator get bufferIndicator => _bufferedSink.bufferIndicator;
+
+ /// Adds [data] immediately to the underlying buffer.
+ ///
+ /// If there is buffered data which was added with [addBufferedData] and it
+ /// has not been flushed with [flushBufferedData] an error will be thrown.
+ void add(List<int> data) {
+ if (_builder.length > 0) {
+ throw new StateError(
+ 'Cannot trigger an asynchronous write while there is buffered data.');
+ }
+ _bufferedSink.sink.add(data);
+ }
+
+ /// Queues up [bytes] to be written.
+ void addBufferedData(List<int> bytes) {
+ _builder.add(bytes);
+ }
+
+ /// Flushes all data which was enqueued by [addBufferedData].
+ void flushBufferedData() {
+ if (_builder.length > 0) {
+ _bufferedSink.sink.add(_builder.takeBytes());
+ }
+ }
+
+ /// Closes this sink.
+ Future close() {
+ flushBufferedData();
+ return _bufferedSink.sink.close().whenComplete(() => doneFuture);
+ }
+
+ /// The future which will complete once this sink has been closed.
+ Future get doneFuture => _bufferedSink.doneFuture;
+}
diff --git a/http2/lib/src/byte_utils.dart b/http2/lib/src/byte_utils.dart
new file mode 100644
index 0000000..756e741
--- /dev/null
+++ b/http2/lib/src/byte_utils.dart
@@ -0,0 +1,59 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library http2.src.byte_utils;
+
+import 'dart:typed_data';
+
+List<int> viewOrSublist(List<int> data, int offset, int length) {
+ if (data is Uint8List) {
+ return new Uint8List.view(data.buffer, data.offsetInBytes + offset, length);
+ } else {
+ return data.sublist(offset, offset + length);
+ }
+}
+
+int readInt64(List<int> bytes, int offset) {
+ int high = readInt32(bytes, offset);
+ int low = readInt32(bytes, offset + 4);
+ return high << 32 | low;
+}
+
+int readInt32(List<int> bytes, int offset) {
+ return (bytes[offset] << 24) |
+ (bytes[offset + 1] << 16) |
+ (bytes[offset + 2] << 8) |
+ bytes[offset + 3];
+}
+
+int readInt24(List<int> bytes, int offset) {
+ return (bytes[offset] << 16) | (bytes[offset + 1] << 8) | bytes[offset + 2];
+}
+
+int readInt16(List<int> bytes, int offset) {
+ return (bytes[offset] << 8) | bytes[offset + 1];
+}
+
+void setInt64(List<int> bytes, int offset, int value) {
+ setInt32(bytes, offset, value >> 32);
+ setInt32(bytes, offset + 4, value & 0xffffffff);
+}
+
+void setInt32(List<int> bytes, int offset, int value) {
+ bytes[offset] = (value >> 24) & 0xff;
+ bytes[offset + 1] = (value >> 16) & 0xff;
+ bytes[offset + 2] = (value >> 8) & 0xff;
+ bytes[offset + 3] = value & 0xff;
+}
+
+void setInt24(List<int> bytes, int offset, int value) {
+ bytes[offset] = (value >> 16) & 0xff;
+ bytes[offset + 1] = (value >> 8) & 0xff;
+ bytes[offset + 2] = value & 0xff;
+}
+
+void setInt16(List<int> bytes, int offset, int value) {
+ bytes[offset] = (value >> 8) & 0xff;
+ bytes[offset + 1] = value & 0xff;
+}
diff --git a/http2/lib/src/connection.dart b/http2/lib/src/connection.dart
new file mode 100644
index 0000000..271b86f
--- /dev/null
+++ b/http2/lib/src/connection.dart
@@ -0,0 +1,490 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library http2.src.conn;
+
+import 'dart:async';
+import 'dart:convert' show utf8;
+
+import '../transport.dart';
+import 'connection_preface.dart';
+import 'flowcontrol/connection_queues.dart';
+import 'flowcontrol/queue_messages.dart';
+import 'flowcontrol/window.dart';
+import 'flowcontrol/window_handler.dart';
+import 'frames/frame_defragmenter.dart';
+import 'frames/frames.dart';
+import 'hpack/hpack.dart';
+import 'ping/ping_handler.dart';
+import 'settings/settings.dart';
+import 'streams/stream_handler.dart';
+import 'sync_errors.dart';
+
+class ConnectionState {
+ /// The connection has been established, we're waiting for the settings frame
+ /// of the remote end.
+ static const int Initialized = 1;
+
+ /// The connection has been established and is fully operational.
+ static const int Operational = 2;
+
+ /// The connection is no longer accepting new streams or creating new streams.
+ static const int Finishing = 3;
+
+ /// The connection has been terminated and cannot be used anymore.
+ static const int Terminated = 4;
+
+ /// Whether we actively were finishing the connection.
+ static const int FinishingActive = 1;
+
+ /// Whether we passively were finishing the connection.
+ static const int FinishingPassive = 2;
+
+ int state = Initialized;
+ int finishingState = 0;
+
+ ConnectionState();
+
+ bool get isInitialized => state == ConnectionState.Initialized;
+
+ bool get isOperational => state == ConnectionState.Operational;
+
+ bool get isFinishing => state == ConnectionState.Finishing;
+
+ bool get isTerminated => state == ConnectionState.Terminated;
+
+ bool get activeFinishing =>
+ state == Finishing && (finishingState & FinishingActive) != 0;
+
+ bool get passiveFinishing =>
+ state == Finishing && (finishingState & FinishingPassive) != 0;
+
+ String toString() {
+ String message = '';
+
+ void add(bool condition, String flag) {
+ if (condition) {
+ if (message.length == 0) {
+ message = flag;
+ } else {
+ message = '$message/$flag';
+ }
+ }
+ }
+
+ add(isInitialized, 'Initialized');
+ add(isOperational, 'IsOperational');
+ add(isFinishing, 'IsFinishing');
+ add(isTerminated, 'IsTerminated');
+ add(activeFinishing, 'ActiveFinishing');
+ add(passiveFinishing, 'PassiveFinishing');
+
+ return message;
+ }
+}
+
+abstract class Connection {
+ /// The settings the other end has acknowledged to use when communicating with
+ /// us.
+ final ActiveSettings acknowledgedSettings = new ActiveSettings();
+
+ /// The settings we have to obey communicating with the other side.
+ final ActiveSettings peerSettings = new ActiveSettings();
+
+ /// Whether this connection is a client connection.
+ final bool isClientConnection;
+
+ /// Active state handler for this connection.
+ ActiveStateHandler onActiveStateChanged;
+
+ /// The HPack context for this connection.
+ final HPackContext _hpackContext = new HPackContext();
+
+ /// The flow window for this connection of the peer.
+ final Window _peerWindow = new Window();
+
+ /// The flow window for this connection of this end.
+ final Window _localWindow = new Window();
+
+ /// Used for defragmenting PushPromise/Header frames.
+ final FrameDefragmenter _defragmenter = new FrameDefragmenter();
+
+ /// The outgoing frames of this connection;
+ FrameWriter _frameWriter;
+
+ /// A subscription of incoming [Frame]s.
+ StreamSubscription<Frame> _frameReaderSubscription;
+
+ /// The incoming connection-level message queue.
+ ConnectionMessageQueueIn _incomingQueue;
+
+ /// The outgoing connection-level message queue.
+ ConnectionMessageQueueOut _outgoingQueue;
+
+ /// The ping handler used for making pings & handling remote pings.
+ PingHandler _pingHandler;
+
+ /// The settings handler used for changing settings & for handling remote
+ /// setting changes.
+ SettingsHandler _settingsHandler;
+
+ /// The set of active streams this connection has.
+ StreamHandler _streams;
+
+ /// The connection-level flow control window handler for outgoing messages.
+ OutgoingConnectionWindowHandler _connectionWindowHandler;
+
+ /// The state of this connection.
+ ConnectionState _state;
+
+ Connection(Stream<List<int>> incoming, StreamSink<List<int>> outgoing,
+ Settings settings,
+ {this.isClientConnection: true}) {
+ _setupConnection(incoming, outgoing, settings);
+ }
+
+ /// Runs all setup necessary before new streams can be created with the remote
+ /// peer.
+ void _setupConnection(Stream<List<int>> incoming,
+ StreamSink<List<int>> outgoing, Settings settingsObject) {
+ // Setup frame reading.
+ var incomingFrames =
+ new FrameReader(incoming, acknowledgedSettings).startDecoding();
+ _frameReaderSubscription = incomingFrames.listen((Frame frame) {
+ _catchProtocolErrors(() => _handleFrameImpl(frame));
+ }, onError: (error, stack) {
+ _terminate(ErrorCode.CONNECT_ERROR, causedByTransportError: true);
+ }, onDone: () {
+ // Ensure existing messages from lower levels are sent to the upper
+ // levels before we terminate everything.
+ _incomingQueue.forceDispatchIncomingMessages();
+ _streams.forceDispatchIncomingMessages();
+
+ _terminate(ErrorCode.CONNECT_ERROR, causedByTransportError: true);
+ });
+
+ // Setup frame writing.
+ _frameWriter =
+ new FrameWriter(_hpackContext.encoder, outgoing, peerSettings);
+ _frameWriter.doneFuture.then((_) {
+ _terminate(ErrorCode.CONNECT_ERROR, causedByTransportError: true);
+ }).catchError((error, stack) {
+ _terminate(ErrorCode.CONNECT_ERROR, causedByTransportError: true);
+ });
+
+ // Setup handlers.
+ _settingsHandler = new SettingsHandler(_hpackContext.encoder, _frameWriter,
+ acknowledgedSettings, peerSettings);
+ _pingHandler = new PingHandler(_frameWriter);
+
+ var settings = _decodeSettings(settingsObject);
+
+ // Do the initial settings handshake (possibly with pushes disabled).
+ _settingsHandler.changeSettings(settings).catchError((error) {
+ // TODO: The [error] can contain sensitive information we now expose via
+ // a [Goaway] frame. We should somehow ensure we're only sending useful
+ // but non-sensitive information.
+ _terminate(ErrorCode.PROTOCOL_ERROR,
+ message: 'Failed to set initial settings (error: $error).');
+ });
+
+ _settingsHandler.onInitialWindowSizeChange.listen((int difference) {
+ _catchProtocolErrors(() {
+ _streams.processInitialWindowSizeSettingChange(difference);
+ });
+ });
+
+ // Setup the connection window handler, which keeps track of the
+ // size of the outgoing connection window.
+ _connectionWindowHandler = new OutgoingConnectionWindowHandler(_peerWindow);
+
+ var connectionWindowUpdater =
+ new IncomingWindowHandler.connection(_frameWriter, _localWindow);
+
+ // Setup queues for outgoing/incoming messages on the connection level.
+ _outgoingQueue =
+ new ConnectionMessageQueueOut(_connectionWindowHandler, _frameWriter);
+ _incomingQueue = new ConnectionMessageQueueIn(
+ connectionWindowUpdater, _catchProtocolErrors);
+
+ if (isClientConnection) {
+ _streams = new StreamHandler.client(
+ _frameWriter,
+ _incomingQueue,
+ _outgoingQueue,
+ _settingsHandler.peerSettings,
+ _settingsHandler.acknowledgedSettings,
+ _activeStateHandler);
+ } else {
+ _streams = new StreamHandler.server(
+ _frameWriter,
+ _incomingQueue,
+ _outgoingQueue,
+ _settingsHandler.peerSettings,
+ _settingsHandler.acknowledgedSettings,
+ _activeStateHandler);
+ }
+
+ // NOTE: We're not waiting until initial settings have been exchanged
+ // before we start using the connection (i.e. we don't wait for half a
+ // round-trip-time).
+ _state = new ConnectionState();
+ }
+
+ List<Setting> _decodeSettings(Settings settings) {
+ var settingsList = <Setting>[];
+
+ // By default a endpoitn can make an unlimited number of concurrent streams.
+ if (settings.concurrentStreamLimit != null) {
+ settingsList.add(new Setting(Setting.SETTINGS_MAX_CONCURRENT_STREAMS,
+ settings.concurrentStreamLimit));
+ }
+
+ // By default the stream level flow control window is 64 KiB.
+ if (settings.streamWindowSize != null) {
+ settingsList.add(new Setting(
+ Setting.SETTINGS_INITIAL_WINDOW_SIZE, settings.streamWindowSize));
+ }
+
+ if (settings is ClientSettings) {
+ // By default the server is allowed to do server pushes.
+ if (settings.allowServerPushes == null ||
+ settings.allowServerPushes == false) {
+ settingsList.add(new Setting(Setting.SETTINGS_ENABLE_PUSH, 0));
+ }
+ } else if (settings is ServerSettings) {
+ // No special server settings at the moment.
+ } else {
+ assert(false);
+ }
+
+ return settingsList;
+ }
+
+ /// Pings the remote peer (can e.g. be used for measuring latency).
+ Future ping() {
+ return _pingHandler.ping().catchError((e, s) {
+ return new Future.error(
+ new TransportException('The connection has been terminated.'));
+ }, test: (e) => e is TerminatedException);
+ }
+
+ /// Finishes this connection.
+ Future finish() {
+ _finishing(active: true);
+
+ // TODO: There is probably more we need to wait for.
+ return _streams.done.whenComplete(() {
+ var futures = [_frameWriter.close()];
+ var f = _frameReaderSubscription.cancel();
+ if (f != null) futures.add(f);
+ return Future.wait(futures);
+ });
+ }
+
+ /// Terminates this connection forcefully.
+ Future terminate() {
+ return _terminate(ErrorCode.NO_ERROR);
+ }
+
+ void _activeStateHandler(bool isActive) {
+ if (onActiveStateChanged != null) {
+ onActiveStateChanged(isActive);
+ }
+ }
+
+ /// Invokes the passed in closure and catches any exceptions.
+ void _catchProtocolErrors(void fn()) {
+ try {
+ fn();
+ } on ProtocolException catch (error) {
+ _terminate(ErrorCode.PROTOCOL_ERROR, message: '$error');
+ } on FlowControlException catch (error) {
+ _terminate(ErrorCode.FLOW_CONTROL_ERROR, message: '$error');
+ } on FrameSizeException catch (error) {
+ _terminate(ErrorCode.FRAME_SIZE_ERROR, message: '$error');
+ } on HPackDecodingException catch (error) {
+ _terminate(ErrorCode.PROTOCOL_ERROR, message: '$error');
+ } on TerminatedException {
+ // We tried to perform an action even though the connection was already
+ // terminated.
+ // TODO: Can this even happen and if so, how should we propagate this
+ // error?
+ } catch (error) {
+ _terminate(ErrorCode.INTERNAL_ERROR, message: '$error');
+ }
+ }
+
+ void _handleFrameImpl(Frame frame) {
+ // The first frame from the other side must be a [SettingsFrame], otherwise
+ // we terminate the connection.
+ if (_state.isInitialized) {
+ if (frame is! SettingsFrame) {
+ _terminate(ErrorCode.PROTOCOL_ERROR,
+ message: 'Expected to first receive a settings frame.');
+ return;
+ }
+ _state.state = ConnectionState.Operational;
+ }
+
+ // Try to defragment [frame] if it is a Headers/PushPromise frame.
+ frame = _defragmenter.tryDefragmentFrame(frame);
+ if (frame == null) return;
+
+ // Try to decode headers if it's a Headers/PushPromise frame.
+ // [This needs to be done even if the frames get ignored, since the entire
+ // connection shares one HPack compression context.]
+ if (frame is HeadersFrame) {
+ frame.decodedHeaders =
+ _hpackContext.decoder.decode(frame.headerBlockFragment);
+ } else if (frame is PushPromiseFrame) {
+ frame.decodedHeaders =
+ _hpackContext.decoder.decode(frame.headerBlockFragment);
+ }
+
+ // Handle the frame as either a connection or a stream frame.
+ if (frame.header.streamId == 0) {
+ if (frame is SettingsFrame) {
+ _settingsHandler.handleSettingsFrame(frame);
+ } else if (frame is PingFrame) {
+ _pingHandler.processPingFrame(frame);
+ } else if (frame is WindowUpdateFrame) {
+ _connectionWindowHandler.processWindowUpdate(frame);
+ } else if (frame is GoawayFrame) {
+ _streams.processGoawayFrame(frame);
+ _finishing(active: false);
+ } else if (frame is UnknownFrame) {
+ // We can safely ignore these.
+ } else {
+ throw new ProtocolException(
+ 'Cannot handle frame type ${frame.runtimeType} with stream-id 0.');
+ }
+ } else {
+ _streams.processStreamFrame(_state, frame);
+ }
+ }
+
+ void _finishing({bool active: true, String message}) {
+ // If this connection is already dead, we return.
+ if (_state.isTerminated) return;
+
+ // If this connection is already finishing, we make sure to store the
+ // passive bit, since this information is used by [StreamHandler].
+ //
+ // Vice versa should not matter: If we started passively finishing, an
+ // active finish should be a NOP.
+ if (_state.isFinishing) {
+ if (!active) _state.finishingState |= ConnectionState.FinishingPassive;
+ return;
+ }
+
+ assert(_state.isInitialized || _state.isOperational);
+
+ // If we are actively finishing this connection, we'll send a
+ // GoawayFrame otherwise we'll just propagate the message.
+ if (active) {
+ _state.state = ConnectionState.Finishing;
+ _state.finishingState |= ConnectionState.FinishingActive;
+
+ _outgoingQueue.enqueueMessage(new GoawayMessage(
+ _streams.highestPeerInitiatedStream,
+ ErrorCode.NO_ERROR,
+ message != null ? utf8.encode(message) : []));
+ } else {
+ _state.state = ConnectionState.Finishing;
+ _state.finishingState |= ConnectionState.FinishingPassive;
+ }
+
+ _streams.startClosing();
+ }
+
+ /// Terminates this connection (if it is not already terminated).
+ ///
+ /// The returned future will never complete with an error.
+ Future _terminate(int errorCode,
+ {bool causedByTransportError: false, String message}) {
+ // TODO: When do we complete here?
+ if (_state.state != ConnectionState.Terminated) {
+ _state.state = ConnectionState.Terminated;
+
+ var cancelFuture = new Future.sync(_frameReaderSubscription.cancel);
+ if (!causedByTransportError) {
+ _outgoingQueue.enqueueMessage(new GoawayMessage(
+ _streams.highestPeerInitiatedStream,
+ errorCode,
+ message != null ? utf8.encode(message) : []));
+ }
+ var closeFuture = _frameWriter.close().catchError((e, s) {
+ // We ignore any errors after writing to [GoawayFrame]
+ });
+
+ // Close all lower level handlers with an error message.
+ // (e.g. if there is a pending connection.ping(), it's returned
+ // Future will complete with this error).
+ var exception = new TransportConnectionException(
+ errorCode, 'Connection is being forcefully terminated.');
+
+ // Close all streams & stream queues
+ _streams.terminate(exception);
+
+ // Close the connection queues
+ _incomingQueue.terminate(exception);
+ _outgoingQueue.terminate(exception);
+
+ _pingHandler.terminate(exception);
+ _settingsHandler.terminate(exception);
+
+ return Future.wait([cancelFuture, closeFuture]).catchError((_) {});
+ }
+ return new Future.value();
+ }
+}
+
+class ClientConnection extends Connection implements ClientTransportConnection {
+ ClientConnection._(Stream<List<int>> incoming, StreamSink<List<int>> outgoing,
+ Settings settings)
+ : super(incoming, outgoing, settings, isClientConnection: true);
+
+ factory ClientConnection(Stream<List<int>> incoming,
+ StreamSink<List<int>> outgoing, ClientSettings clientSettings) {
+ outgoing.add(CONNECTION_PREFACE);
+ return new ClientConnection._(incoming, outgoing, clientSettings);
+ }
+
+ bool get isOpen =>
+ !_state.isFinishing && !_state.isTerminated && _streams.canOpenStream;
+
+ ClientTransportStream makeRequest(List<Header> headers,
+ {bool endStream: false}) {
+ if (_state.isFinishing) {
+ throw new StateError(
+ 'The http/2 connection is finishing and can therefore not be used to '
+ 'make new streams.');
+ } else if (_state.isTerminated) {
+ throw new StateError(
+ 'The http/2 connection is no longer active and can therefore not be '
+ 'used to make new streams.');
+ }
+ var hStream = _streams.newStream(headers, endStream: endStream);
+ if (_streams.ranOutOfStreamIds) {
+ _finishing(active: true, message: 'Ran out of stream ids');
+ }
+ return hStream;
+ }
+}
+
+class ServerConnection extends Connection implements ServerTransportConnection {
+ ServerConnection._(Stream<List<int>> incoming, StreamSink<List<int>> outgoing,
+ Settings settings)
+ : super(incoming, outgoing, settings, isClientConnection: false);
+
+ factory ServerConnection(Stream<List<int>> incoming,
+ StreamSink<List<int>> outgoing, ServerSettings serverSettings) {
+ var frameBytes = readConnectionPreface(incoming);
+ return new ServerConnection._(frameBytes, outgoing, serverSettings);
+ }
+
+ Stream<ServerTransportStream> get incomingStreams =>
+ _streams.incomingStreams.cast<ServerTransportStream>();
+}
diff --git a/http2/lib/src/connection_preface.dart b/http2/lib/src/connection_preface.dart
new file mode 100644
index 0000000..08cf7e8
--- /dev/null
+++ b/http2/lib/src/connection_preface.dart
@@ -0,0 +1,118 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library http2.src.connection_preface;
+
+import 'dart:async';
+import 'dart:math';
+
+import 'byte_utils.dart';
+
+/// This is a set of bytes with which a client connection begins in the normal
+/// case. It can be used on a server to distinguish HTTP/1.1 and HTTP/2 clients.
+const List<int> CONNECTION_PREFACE = const [
+ 0x50,
+ 0x52,
+ 0x49,
+ 0x20,
+ 0x2a,
+ 0x20,
+ 0x48,
+ 0x54,
+ 0x54,
+ 0x50,
+ 0x2f,
+ 0x32,
+ 0x2e,
+ 0x30,
+ 0x0d,
+ 0x0a,
+ 0x0d,
+ 0x0a,
+ 0x53,
+ 0x4d,
+ 0x0d,
+ 0x0a,
+ 0x0d,
+ 0x0a
+];
+
+/// Reads the connection preface from [incoming].
+///
+/// The returned `Stream` will be a duplicate of `incoming` without the
+/// connection preface. If an error occurs while reading the connection
+/// preface, the returned stream will have only an error.
+Stream<List<int>> readConnectionPreface(Stream<List<int>> incoming) {
+ StreamController<List<int>> result;
+ StreamSubscription subscription;
+ bool connectionPrefaceRead = false;
+ var prefaceBuffer = <int>[];
+ bool terminated = false;
+
+ terminate(error) {
+ if (!terminated) {
+ result.addError(error);
+ result.close();
+ subscription.cancel();
+ }
+ terminated = true;
+ }
+
+ bool compareConnectionPreface(List<int> data) {
+ for (int i = 0; i < CONNECTION_PREFACE.length; i++) {
+ if (data[i] != CONNECTION_PREFACE[i]) {
+ terminate('Connection preface does not match.');
+ return false;
+ }
+ }
+ prefaceBuffer = null;
+ connectionPrefaceRead = true;
+ return true;
+ }
+
+ void onData(List<int> data) {
+ if (connectionPrefaceRead) {
+ // Forward data after reading preface.
+ result.add(data);
+ } else {
+ if (prefaceBuffer.isEmpty && data.length > CONNECTION_PREFACE.length) {
+ if (!compareConnectionPreface(data)) return;
+ data = data.sublist(CONNECTION_PREFACE.length);
+ } else if (prefaceBuffer.length < CONNECTION_PREFACE.length) {
+ int remaining = CONNECTION_PREFACE.length - prefaceBuffer.length;
+
+ int end = min(data.length, remaining);
+ var part1 = viewOrSublist(data, 0, end);
+ var part2 = viewOrSublist(data, end, data.length - end);
+ prefaceBuffer.addAll(part1);
+
+ if (prefaceBuffer.length == CONNECTION_PREFACE.length) {
+ if (!compareConnectionPreface(prefaceBuffer)) return;
+ }
+ data = part2;
+ }
+ if (data.length > 0) {
+ result.add(data);
+ }
+ }
+ }
+
+ result = new StreamController(
+ onListen: () {
+ subscription = incoming.listen(onData,
+ onError: (e, StackTrace s) => result.addError(e, s),
+ onDone: () {
+ if (prefaceBuffer != null) {
+ terminate('EOS before connection preface could be read.');
+ } else {
+ result.close();
+ }
+ });
+ },
+ onPause: () => subscription.pause(),
+ onResume: () => subscription.resume(),
+ onCancel: () => subscription.cancel());
+
+ return result.stream;
+}
diff --git a/http2/lib/src/error_handler.dart b/http2/lib/src/error_handler.dart
new file mode 100644
index 0000000..ec1935d
--- /dev/null
+++ b/http2/lib/src/error_handler.dart
@@ -0,0 +1,107 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library http2.src.error_handler;
+
+import 'dart:async';
+
+import 'sync_errors.dart';
+
+/// Used by classes which may be terminated from the outside.
+class TerminatableMixin {
+ bool _terminated = false;
+
+ /// Terminates this stream message queue. Further operations on it will fail.
+ void terminate([error]) {
+ if (!wasTerminated) {
+ _terminated = true;
+ onTerminated(error);
+ }
+ }
+
+ bool get wasTerminated => _terminated;
+
+ void onTerminated(error) {
+ // Subclasses can override this method if they want.
+ }
+
+ T ensureNotTerminatedSync<T>(T f()) {
+ if (wasTerminated) {
+ throw new TerminatedException();
+ }
+ return f();
+ }
+
+ Future ensureNotTerminatedAsync(Future f()) {
+ if (wasTerminated) {
+ return new Future.error(new TerminatedException());
+ }
+ return f();
+ }
+}
+
+/// Used by classes which may be cancelled.
+class CancellableMixin {
+ bool _cancelled = false;
+ final _cancelCompleter = new Completer<void>.sync();
+
+ Future<void> get onCancel => _cancelCompleter.future;
+
+ /// Cancel this stream message queue. Further operations on it will fail.
+ void cancel() {
+ if (!wasCancelled) {
+ _cancelled = true;
+ _cancelCompleter.complete();
+ }
+ }
+
+ bool get wasCancelled => _cancelled;
+}
+
+/// Used by classes which may be closed.
+class ClosableMixin {
+ bool _closing = false;
+ final Completer _completer = new Completer();
+
+ Future get done => _completer.future;
+
+ bool get isClosing => _closing;
+ bool get wasClosed => _completer.isCompleted;
+
+ void startClosing() {
+ if (!_closing) {
+ _closing = true;
+
+ onClosing();
+ }
+ onCheckForClose();
+ }
+
+ void onCheckForClose() {
+ // Subclasses can override this method if they want.
+ }
+
+ void onClosing() {
+ // Subclasses can override this method if they want.
+ }
+
+ dynamic ensureNotClosingSync(f()) {
+ if (isClosing) {
+ throw new StateError('Was in the process of closing.');
+ }
+ return f();
+ }
+
+ void closeWithValue([value]) {
+ if (!wasClosed) {
+ _completer.complete(value);
+ }
+ }
+
+ void closeWithError(error) {
+ if (!wasClosed) {
+ _completer.complete(error);
+ }
+ }
+}
diff --git a/http2/lib/src/flowcontrol/connection_queues.dart b/http2/lib/src/flowcontrol/connection_queues.dart
new file mode 100644
index 0000000..4d28db2
--- /dev/null
+++ b/http2/lib/src/flowcontrol/connection_queues.dart
@@ -0,0 +1,355 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+// TODO: Take priorities into account.
+// TODO: Properly fragment large data frames, so they are not taking up too much
+// bandwidth.
+library http2.src.flowcontrol.connection_flow_controller;
+
+import 'dart:async';
+import 'dart:collection';
+
+import '../../transport.dart';
+
+import '../byte_utils.dart';
+import '../error_handler.dart';
+import '../frames/frames.dart';
+
+import 'queue_messages.dart';
+import 'stream_queues.dart';
+import 'window_handler.dart';
+
+/// The last place before messages coming from the application get encoded and
+/// send as [Frame]s.
+///
+/// It will convert [Message]s from higher layers and send them via [Frame]s.
+///
+/// - It will queue messages until the connection-level flow control window
+/// allows sending the message and the underlying [StreamSink] is not
+/// buffering.
+/// - It will use a [FrameWriter] to write a new frame to the connection.
+// TODO: Make [StreamsHandler] call [connectionOut.startClosing()] once
+// * all streams have been closed
+// * the connection state is finishing
+class ConnectionMessageQueueOut extends Object
+ with TerminatableMixin, ClosableMixin {
+ /// The handler which will be used for increasing the connection-level flow
+ /// control window.
+ final OutgoingConnectionWindowHandler _connectionWindow;
+
+ /// The buffered [Message]s which are to be delivered to the remote peer.
+ final Queue<Message> _messages = new Queue<Message>();
+
+ /// The [FrameWriter] used for writing Headers/Data/PushPromise frames.
+ final FrameWriter _frameWriter;
+
+ ConnectionMessageQueueOut(this._connectionWindow, this._frameWriter) {
+ _frameWriter.bufferIndicator.bufferEmptyEvents.listen((_) {
+ _trySendMessages();
+ });
+ _connectionWindow.positiveWindow.bufferEmptyEvents.listen((_) {
+ _trySendMessages();
+ });
+ }
+
+ /// The number of pending messages which haven't been written to the wire.
+ int get pendingMessages => _messages.length;
+
+ /// Enqueues a new [Message] which should be delivered to the remote peer.
+ void enqueueMessage(Message message) {
+ ensureNotClosingSync(() {
+ if (!wasTerminated) {
+ _messages.addLast(message);
+ _trySendMessages();
+ }
+ });
+ }
+
+ void onTerminated(error) {
+ _messages.clear();
+ closeWithError(error);
+ }
+
+ void onCheckForClose() {
+ if (isClosing && _messages.length == 0) {
+ closeWithValue();
+ }
+ }
+
+ void _trySendMessages() {
+ if (!wasTerminated) {
+ // We can make progress if
+ // * there is at least one message to send
+ // * the underlying frame writer / sink / socket doesn't block
+ // * either one
+ // * the next message is a non-flow control message (e.g. headers)
+ // * the connection window is positive
+
+ if (_messages.length > 0 &&
+ !_frameWriter.bufferIndicator.wouldBuffer &&
+ (!_connectionWindow.positiveWindow.wouldBuffer ||
+ _messages.first is! DataMessage)) {
+ _trySendMessage();
+
+ // If we have more messages and we can send them, we'll run them
+ // using `Timer.run()` to let other things get in-between.
+ if (_messages.length > 0 &&
+ !_frameWriter.bufferIndicator.wouldBuffer &&
+ (!_connectionWindow.positiveWindow.wouldBuffer ||
+ _messages.first is! DataMessage)) {
+ // TODO: If all the frame writer methods would return the
+ // number of bytes written, we could just say, we loop here until 10kb
+ // and after words, we'll make `Timer.run()`.
+ Timer.run(_trySendMessages);
+ } else {
+ onCheckForClose();
+ }
+ }
+ }
+ }
+
+ void _trySendMessage() {
+ Message message = _messages.first;
+ if (message is HeadersMessage) {
+ _messages.removeFirst();
+ _frameWriter.writeHeadersFrame(message.streamId, message.headers,
+ endStream: message.endStream);
+ } else if (message is PushPromiseMessage) {
+ _messages.removeFirst();
+ _frameWriter.writePushPromiseFrame(
+ message.streamId, message.promisedStreamId, message.headers);
+ } else if (message is DataMessage) {
+ _messages.removeFirst();
+
+ if (_connectionWindow.peerWindowSize >= message.bytes.length) {
+ _connectionWindow.decreaseWindow(message.bytes.length);
+ _frameWriter.writeDataFrame(message.streamId, message.bytes,
+ endStream: message.endStream);
+ } else {
+ // NOTE: We need to fragment the DataMessage.
+ // TODO: Do not fragment if the number of bytes we can send is too low
+ int len = _connectionWindow.peerWindowSize;
+ var head = viewOrSublist(message.bytes, 0, len);
+ var tail =
+ viewOrSublist(message.bytes, len, message.bytes.length - len);
+
+ _connectionWindow.decreaseWindow(head.length);
+ _frameWriter.writeDataFrame(message.streamId, head, endStream: false);
+
+ var tailMessage =
+ new DataMessage(message.streamId, tail, message.endStream);
+ _messages.addFirst(tailMessage);
+ }
+ } else if (message is ResetStreamMessage) {
+ _messages.removeFirst();
+ _frameWriter.writeRstStreamFrame(message.streamId, message.errorCode);
+ } else if (message is GoawayMessage) {
+ _messages.removeFirst();
+ _frameWriter.writeGoawayFrame(
+ message.lastStreamId, message.errorCode, message.debugData);
+ } else {
+ throw new StateError(
+ 'Unexpected message in queue: ${message.runtimeType}');
+ }
+ }
+}
+
+/// The first place an incoming stream message gets delivered to.
+///
+/// The [ConnectionMessageQueueIn] will be given [Frame]s which were sent to
+/// any stream on this connection.
+///
+/// - It will extract the necessary data from the [Frame] and store it in a new
+/// [Message] object.
+/// - It will multiplex the created [Message]es to a stream-specific
+/// [StreamMessageQueueIn].
+/// - If the [StreamMessageQueueIn] cannot accept more data, the data will be
+/// buffered until it can.
+/// - [DataMessage]s which have been successfully delivered to a stream-specific
+/// [StreamMessageQueueIn] will increase the flow control window for the
+/// connection.
+///
+/// Incoming [DataFrame]s will decrease the flow control window the peer has
+/// available.
+// TODO: Make [StreamsHandler] call [connectionOut.startClosing()] once
+// * all streams have been closed
+// * the connection state is finishing
+class ConnectionMessageQueueIn extends Object
+ with TerminatableMixin, ClosableMixin {
+ /// The handler which will be used for increasing the connection-level flow
+ /// control window.
+ final IncomingWindowHandler _windowUpdateHandler;
+
+ /// Catches any protocol errors and acts upon them.
+ final Function _catchProtocolErrors;
+
+ /// A mapping from stream-id to the corresponding stream-specific
+ /// [StreamMessageQueueIn].
+ final Map<int, StreamMessageQueueIn> _stream2messageQueue = {};
+
+ /// A buffer for [Message]s which cannot be received by their
+ /// [StreamMessageQueueIn].
+ final Map<int, Queue<Message>> _stream2pendingMessages = {};
+
+ /// The number of pending messages which haven't been delivered
+ /// to the stream-specific queue. (for debugging purposes)
+ int _count = 0;
+
+ ConnectionMessageQueueIn(
+ this._windowUpdateHandler, this._catchProtocolErrors);
+
+ void onTerminated(error) {
+ // NOTE: The higher level will be shutdown first, so all streams
+ // should have been removed at this point.
+ assert(_stream2messageQueue.isEmpty);
+ assert(_stream2pendingMessages.isEmpty);
+ closeWithError(error);
+ }
+
+ void onCheckForClose() {
+ if (isClosing) {
+ assert(_stream2messageQueue.isEmpty == _stream2pendingMessages.isEmpty);
+ if (_stream2messageQueue.isEmpty) {
+ closeWithValue();
+ }
+ }
+ }
+
+ /// The number of pending messages which haven't been delivered
+ /// to the stream-specific queue. (for debugging purposes)
+ int get pendingMessages => _count;
+
+ /// Registers a stream specific [StreamMessageQueueIn] for a new stream id.
+ void insertNewStreamMessageQueue(int streamId, StreamMessageQueueIn mq) {
+ if (_stream2messageQueue.containsKey(streamId)) {
+ throw new ArgumentError(
+ 'Cannot register a SteramMessageQueueIn for the same streamId '
+ 'multiple times');
+ }
+
+ var pendingMessages = new Queue<Message>();
+ _stream2pendingMessages[streamId] = pendingMessages;
+ _stream2messageQueue[streamId] = mq;
+
+ mq.bufferIndicator.bufferEmptyEvents.listen((_) {
+ _catchProtocolErrors(() {
+ _tryDispatch(streamId, mq, pendingMessages);
+ });
+ });
+ }
+
+ /// Removes a stream id and its message queue from this connection-level
+ /// message queue.
+ void removeStreamMessageQueue(int streamId) {
+ _stream2pendingMessages.remove(streamId);
+ _stream2messageQueue.remove(streamId);
+ }
+
+ /// Processes an incoming [DataFrame] which is addressed to a specific stream.
+ void processDataFrame(DataFrame frame) {
+ var streamId = frame.header.streamId;
+ var message =
+ new DataMessage(streamId, frame.bytes, frame.hasEndStreamFlag);
+
+ _windowUpdateHandler.gotData(message.bytes.length);
+ _addMessage(streamId, message);
+ }
+
+ /// If a [DataFrame] will be ignored, this method will take the minimal
+ /// action necessary.
+ void processIgnoredDataFrame(DataFrame frame) {
+ _windowUpdateHandler.gotData(frame.bytes.length);
+ }
+
+ /// Processes an incoming [HeadersFrame] which is addressed to a specific
+ /// stream.
+ void processHeadersFrame(HeadersFrame frame) {
+ var streamId = frame.header.streamId;
+ var message = new HeadersMessage(
+ streamId, frame.decodedHeaders, frame.hasEndStreamFlag);
+ // NOTE: Header frames do not affect flow control - only data frames do.
+ _addMessage(streamId, message);
+ }
+
+ /// Processes an incoming [PushPromiseFrame] which is addressed to a specific
+ /// stream.
+ void processPushPromiseFrame(
+ PushPromiseFrame frame, ClientTransportStream pushedStream) {
+ var streamId = frame.header.streamId;
+ var message = new PushPromiseMessage(streamId, frame.decodedHeaders,
+ frame.promisedStreamId, pushedStream, false);
+
+ // NOTE:
+ // * Header frames do not affect flow control - only data frames do.
+ // * At this point we might reorder a push message earlier than
+ // data/headers messages.
+ _addPushMessage(streamId, message);
+ }
+
+ void _addMessage(int streamId, Message message) {
+ _count++;
+
+ // TODO: Do we need to do a runtime check here and
+ // raise a protocol error if we cannot find the registered stream?
+ var streamMQ = _stream2messageQueue[streamId];
+ var pendingMessages = _stream2pendingMessages[streamId];
+ pendingMessages.addLast(message);
+ _tryDispatch(streamId, streamMQ, pendingMessages);
+ }
+
+ void _addPushMessage(int streamId, PushPromiseMessage message) {
+ _count++;
+
+ // TODO: Do we need to do a runtime check here and
+ // raise a protocol error if we cannot find the registered stream?
+ var streamMQ = _stream2messageQueue[streamId];
+ streamMQ.enqueueMessage(message);
+ }
+
+ void _tryDispatch(
+ int streamId, StreamMessageQueueIn mq, Queue<Message> pendingMessages) {
+ int bytesDeliveredToStream = 0;
+ while (!mq.bufferIndicator.wouldBuffer && pendingMessages.length > 0) {
+ _count--;
+
+ var message = pendingMessages.removeFirst();
+ if (message is DataMessage) {
+ bytesDeliveredToStream += message.bytes.length;
+ }
+ mq.enqueueMessage(message);
+ if (message.endStream) {
+ assert(pendingMessages.isEmpty);
+
+ _stream2messageQueue.remove(streamId);
+ _stream2pendingMessages.remove(streamId);
+ }
+ }
+ if (bytesDeliveredToStream > 0) {
+ _windowUpdateHandler.dataProcessed(bytesDeliveredToStream);
+ }
+
+ onCheckForClose();
+ }
+
+ void forceDispatchIncomingMessages() {
+ final toBeRemoved = new Set<int>();
+ _stream2pendingMessages.forEach((int streamId, Queue<Message> messages) {
+ final mq = _stream2messageQueue[streamId];
+ while (messages.isNotEmpty) {
+ _count--;
+ final message = messages.removeFirst();
+ mq.enqueueMessage(message);
+ if (message.endStream) {
+ toBeRemoved.add(streamId);
+ break;
+ }
+ }
+ });
+
+ for (final streamId in toBeRemoved) {
+ _stream2messageQueue.remove(streamId);
+ _stream2pendingMessages.remove(streamId);
+ }
+ }
+}
diff --git a/http2/lib/src/flowcontrol/queue_messages.dart b/http2/lib/src/flowcontrol/queue_messages.dart
new file mode 100644
index 0000000..b30ab3f
--- /dev/null
+++ b/http2/lib/src/flowcontrol/queue_messages.dart
@@ -0,0 +1,72 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library http2.src.flowcontrol.queue_messages;
+
+import '../../transport.dart';
+
+/// The subclasses of [Message] are objects that are coming from the
+/// connection layer on top of frames.
+///
+/// Messages on a HTTP/2 stream will be represented by a different class
+/// hierarchy.
+abstract class Message {
+ final int streamId;
+ final bool endStream;
+
+ Message(this.streamId, this.endStream);
+}
+
+class HeadersMessage extends Message {
+ final List<Header> headers;
+
+ HeadersMessage(int streamId, this.headers, bool endStream)
+ : super(streamId, endStream);
+
+ String toString() =>
+ 'HeadersMessage(headers: ${headers.length}, endStream: $endStream)';
+}
+
+class DataMessage extends Message {
+ final List<int> bytes;
+
+ DataMessage(int streamId, this.bytes, bool endStream)
+ : super(streamId, endStream);
+
+ String toString() =>
+ 'DataMessage(bytes: ${bytes.length}, endStream: $endStream)';
+}
+
+class PushPromiseMessage extends Message {
+ final List<Header> headers;
+ final int promisedStreamId;
+ final ClientTransportStream pushedStream;
+
+ PushPromiseMessage(int streamId, this.headers, this.promisedStreamId,
+ this.pushedStream, bool endStream)
+ : super(streamId, endStream);
+
+ String toString() => 'PushPromiseMessage(bytes: ${headers.length}, '
+ 'promisedStreamId: $promisedStreamId, endStream: $endStream)';
+}
+
+class ResetStreamMessage extends Message {
+ final int errorCode;
+
+ ResetStreamMessage(int streamId, this.errorCode) : super(streamId, false);
+
+ String toString() => 'ResetStreamMessage(errorCode: $errorCode)';
+}
+
+class GoawayMessage extends Message {
+ final int lastStreamId;
+ final int errorCode;
+ final List<int> debugData;
+
+ GoawayMessage(this.lastStreamId, this.errorCode, this.debugData)
+ : super(0, false);
+
+ String toString() => 'GoawayMessage(lastStreamId: ${lastStreamId}, '
+ 'errorCode: ${errorCode}, debugData: ${debugData.length})';
+}
diff --git a/http2/lib/src/flowcontrol/stream_queues.dart b/http2/lib/src/flowcontrol/stream_queues.dart
new file mode 100644
index 0000000..dc6d2f2
--- /dev/null
+++ b/http2/lib/src/flowcontrol/stream_queues.dart
@@ -0,0 +1,331 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library http2.src.flowcontrol.stream_queues;
+
+import 'dart:async';
+import 'dart:collection';
+
+import '../../transport.dart';
+import '../async_utils/async_utils.dart';
+import '../byte_utils.dart';
+import '../error_handler.dart';
+
+import 'connection_queues.dart';
+import 'queue_messages.dart';
+import 'window_handler.dart';
+
+/// This class will buffer any headers/data messages in the order they were
+/// added.
+///
+/// It will ensure that we never send more data than the remote flow control
+/// window allows.
+class StreamMessageQueueOut extends Object
+ with TerminatableMixin, ClosableMixin {
+ /// The id of the stream this message queue belongs to.
+ final int streamId;
+
+ /// The stream-level flow control handler.
+ final OutgoingStreamWindowHandler streamWindow;
+
+ /// The underlying connection-level message queue.
+ final ConnectionMessageQueueOut connectionMessageQueue;
+
+ /// A indicator for whether this queue is currently buffering.
+ final BufferIndicator bufferIndicator = new BufferIndicator();
+
+ /// Buffered [Message]s which will be written to the underlying connection
+ /// if the flow control window allows so.
+ final Queue<Message> _messages = new Queue<Message>();
+
+ /// Debugging data on how much data should be written to the underlying
+ /// connection message queue.
+ int toBeWrittenBytes = 0;
+
+ /// Debugging data on how much data was written to the underlying connection
+ /// message queue.
+ int writtenBytes = 0;
+
+ StreamMessageQueueOut(
+ this.streamId, this.streamWindow, this.connectionMessageQueue) {
+ streamWindow.positiveWindow.bufferEmptyEvents.listen((_) {
+ if (!wasTerminated) {
+ _trySendData();
+ }
+ });
+ if (streamWindow.positiveWindow.wouldBuffer) {
+ bufferIndicator.markBuffered();
+ } else {
+ bufferIndicator.markUnBuffered();
+ }
+ }
+
+ /// Debugging data about how many messages are pending to be written to the
+ /// connection message queue.
+ int get pendingMessages => _messages.length;
+
+ /// Enqueues a new [Message] which is to be delivered to the connection
+ /// message queue.
+ void enqueueMessage(Message message) {
+ if (message is! ResetStreamMessage) ensureNotClosingSync(() {});
+ if (!wasTerminated) {
+ if (message.endStream) startClosing();
+
+ if (message is DataMessage) {
+ toBeWrittenBytes += message.bytes.length;
+ }
+
+ _messages.addLast(message);
+ _trySendData();
+
+ if (_messages.length > 0) {
+ bufferIndicator.markBuffered();
+ }
+ }
+ }
+
+ void onTerminated(error) {
+ _messages.clear();
+ closeWithError(error);
+ }
+
+ void onCheckForClose() {
+ if (isClosing && _messages.isEmpty) closeWithValue();
+ }
+
+ void _trySendData() {
+ int queueLenBefore = _messages.length;
+
+ while (_messages.length > 0) {
+ Message message = _messages.first;
+
+ if (message is HeadersMessage) {
+ _messages.removeFirst();
+ connectionMessageQueue.enqueueMessage(message);
+ } else if (message is DataMessage) {
+ int bytesAvailable = streamWindow.peerWindowSize;
+ if (bytesAvailable > 0 || message.bytes.length == 0) {
+ _messages.removeFirst();
+
+ // Do we need to fragment?
+ DataMessage messageToSend = message;
+ List<int> messageBytes = message.bytes;
+ // TODO: Do not fragment if the number of bytes we can send is too low
+ if (messageBytes.length > bytesAvailable) {
+ var partA = viewOrSublist(messageBytes, 0, bytesAvailable);
+ var partB = viewOrSublist(messageBytes, bytesAvailable,
+ messageBytes.length - bytesAvailable);
+ var messageA = new DataMessage(message.streamId, partA, false);
+ var messageB =
+ new DataMessage(message.streamId, partB, message.endStream);
+
+ // Put the second fragment back into the front of the queue.
+ _messages.addFirst(messageB);
+
+ // Send the first fragment.
+ messageToSend = messageA;
+ }
+
+ writtenBytes += messageToSend.bytes.length;
+ streamWindow.decreaseWindow(messageToSend.bytes.length);
+ connectionMessageQueue.enqueueMessage(messageToSend);
+ } else {
+ break;
+ }
+ } else if (message is ResetStreamMessage) {
+ _messages.removeFirst();
+ connectionMessageQueue.enqueueMessage(message);
+ } else {
+ throw new StateError('Unknown messages type: ${message.runtimeType}');
+ }
+ }
+ if (queueLenBefore > 0 && _messages.isEmpty) {
+ bufferIndicator.markUnBuffered();
+ }
+
+ onCheckForClose();
+ }
+}
+
+/// Keeps a list of [Message] which should be delivered to the
+/// [TransportStream].
+///
+/// It will keep messages up to the stream flow control window size if the
+/// [messages] listener is paused.
+class StreamMessageQueueIn extends Object
+ with TerminatableMixin, ClosableMixin, CancellableMixin {
+ /// The stream-level window our peer is using when sending us messages.
+ final IncomingWindowHandler windowHandler;
+
+ /// A indicator whether this [StreamMessageQueueIn] is currently buffering.
+ final BufferIndicator bufferIndicator = new BufferIndicator();
+
+ /// The pending [Message]s which are to be delivered via the [messages]
+ /// stream.
+ final Queue<Message> _pendingMessages = new Queue<Message>();
+
+ /// The [StreamController] used for producing the [messages] stream.
+ StreamController<StreamMessage> _incomingMessagesC;
+
+ /// The [StreamController] used for producing the [serverPushes] stream.
+ StreamController<TransportStreamPush> _serverPushStreamsC;
+
+ StreamMessageQueueIn(this.windowHandler) {
+ // We start by marking it as buffered, since no one is listening yet and
+ // incoming messages will get buffered.
+ bufferIndicator.markBuffered();
+
+ _incomingMessagesC = new StreamController(
+ onListen: () {
+ if (!wasClosed && !wasTerminated) {
+ _tryDispatch();
+ _tryUpdateBufferIndicator();
+ }
+ },
+ onPause: () {
+ _tryUpdateBufferIndicator();
+ // TODO: Would we ever want to decrease the window size in this
+ // situation?
+ },
+ onResume: () {
+ if (!wasClosed && !wasTerminated) {
+ _tryDispatch();
+ _tryUpdateBufferIndicator();
+ }
+ },
+ onCancel: cancel);
+
+ _serverPushStreamsC = new StreamController(onListen: () {
+ if (!wasClosed && !wasTerminated) {
+ _tryDispatch();
+ _tryUpdateBufferIndicator();
+ }
+ });
+ }
+
+ /// Debugging data: the number of pending messages in this queue.
+ int get pendingMessages => _pendingMessages.length;
+
+ /// The stream of [StreamMessage]s which come from the remote peer.
+ Stream<StreamMessage> get messages => _incomingMessagesC.stream;
+
+ /// The stream of [TransportStreamPush]es which come from the remote peer.
+ Stream<TransportStreamPush> get serverPushes => _serverPushStreamsC.stream;
+
+ /// A lower layer enqueues a new [Message] which should be delivered to the
+ /// app.
+ void enqueueMessage(Message message) {
+ ensureNotClosingSync(() {
+ if (!wasTerminated) {
+ if (message is PushPromiseMessage) {
+ // NOTE: If server pushes were enabled, the client is responsible for
+ // either rejecting or handling them.
+ assert(!_serverPushStreamsC.isClosed);
+ var transportStreamPush =
+ new TransportStreamPush(message.headers, message.pushedStream);
+ _serverPushStreamsC.add(transportStreamPush);
+ return;
+ }
+
+ if (message is DataMessage) {
+ windowHandler.gotData(message.bytes.length);
+ }
+ _pendingMessages.add(message);
+ if (message.endStream) startClosing();
+
+ _tryDispatch();
+ _tryUpdateBufferIndicator();
+ }
+ });
+ }
+
+ void onTerminated(exception) {
+ _pendingMessages.clear();
+ if (!wasClosed) {
+ if (exception != null) {
+ _incomingMessagesC.addError(exception);
+ }
+ _incomingMessagesC.close();
+ _serverPushStreamsC.close();
+ closeWithError(exception);
+ }
+ }
+
+ void onCloseCheck() {
+ if (isClosing && !wasClosed && _pendingMessages.isEmpty) {
+ _incomingMessagesC.close();
+ _serverPushStreamsC.close();
+ closeWithValue();
+ }
+ }
+
+ void forceDispatchIncomingMessages() {
+ while (_pendingMessages.isNotEmpty) {
+ final message = _pendingMessages.removeFirst();
+ assert(!_incomingMessagesC.isClosed);
+ if (message is HeadersMessage) {
+ _incomingMessagesC.add(new HeadersStreamMessage(message.headers,
+ endStream: message.endStream));
+ } else if (message is DataMessage) {
+ if (message.bytes.length > 0) {
+ _incomingMessagesC.add(new DataStreamMessage(message.bytes,
+ endStream: message.endStream));
+ }
+ } else {
+ // This can never happen.
+ assert(false);
+ }
+ if (message.endStream) {
+ onCloseCheck();
+ }
+ }
+ }
+
+ void _tryDispatch() {
+ while (!wasTerminated && _pendingMessages.isNotEmpty) {
+ bool handled = wasCancelled;
+
+ var message = _pendingMessages.first;
+ if (wasCancelled) {
+ _pendingMessages.removeFirst();
+ } else if (message is HeadersMessage || message is DataMessage) {
+ assert(!_incomingMessagesC.isClosed);
+ if (_incomingMessagesC.hasListener && !_incomingMessagesC.isPaused) {
+ _pendingMessages.removeFirst();
+ if (message is HeadersMessage) {
+ // NOTE: Header messages do not affect flow control - only
+ // data messages do.
+ _incomingMessagesC.add(new HeadersStreamMessage(message.headers,
+ endStream: message.endStream));
+ } else if (message is DataMessage) {
+ if (message.bytes.length > 0) {
+ _incomingMessagesC.add(new DataStreamMessage(message.bytes,
+ endStream: message.endStream));
+ windowHandler.dataProcessed(message.bytes.length);
+ }
+ } else {
+ // This can never happen.
+ assert(false);
+ }
+ handled = true;
+ }
+ }
+ if (handled) {
+ if (message.endStream) {
+ onCloseCheck();
+ }
+ } else {
+ break;
+ }
+ }
+ }
+
+ void _tryUpdateBufferIndicator() {
+ if (_incomingMessagesC.isPaused || _pendingMessages.length > 0) {
+ bufferIndicator.markBuffered();
+ } else if (bufferIndicator.wouldBuffer && !_incomingMessagesC.isPaused) {
+ bufferIndicator.markUnBuffered();
+ }
+ }
+}
diff --git a/http2/lib/src/flowcontrol/window.dart b/http2/lib/src/flowcontrol/window.dart
new file mode 100644
index 0000000..10ba3f7
--- /dev/null
+++ b/http2/lib/src/flowcontrol/window.dart
@@ -0,0 +1,26 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library http2.src.window;
+
+class Window {
+ static const int MAX_WINDOW_SIZE = (1 << 31) - 1;
+
+ /// The size available in this window.
+ ///
+ /// The default flow control window for the entire connection and for new
+ /// streams is 65535).
+ ///
+ /// NOTE: This value can potentially become negative.
+ int _size;
+
+ Window({int initialSize: (1 << 16) - 1}) : _size = initialSize;
+
+ /// The current size of the flow control window.
+ int get size => _size;
+
+ void modify(int difference) {
+ _size += difference;
+ }
+}
diff --git a/http2/lib/src/flowcontrol/window_handler.dart b/http2/lib/src/flowcontrol/window_handler.dart
new file mode 100644
index 0000000..909fa2b
--- /dev/null
+++ b/http2/lib/src/flowcontrol/window_handler.dart
@@ -0,0 +1,164 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library http2.src.window_handler;
+
+import '../async_utils/async_utils.dart';
+import '../frames/frames.dart';
+import '../sync_errors.dart';
+
+import 'window.dart';
+
+abstract class AbstractOutgoingWindowHandler {
+ /// The connection flow control window.
+ final Window _peerWindow;
+
+ /// Indicates when the outgoing connection window turned positive and we can
+ /// send data frames again.
+ final BufferIndicator positiveWindow = new BufferIndicator();
+
+ AbstractOutgoingWindowHandler(this._peerWindow) {
+ if (_peerWindow.size > 0) {
+ positiveWindow.markUnBuffered();
+ }
+ }
+
+ /// The flow control window size we use for sending data. We are not allowed
+ /// to let this window be negative.
+ int get peerWindowSize => _peerWindow.size;
+
+ /// Process a window update frame received from the remote end.
+ void processWindowUpdate(WindowUpdateFrame frame) {
+ int increment = frame.windowSizeIncrement;
+ if ((_peerWindow.size + increment) > Window.MAX_WINDOW_SIZE) {
+ throw new FlowControlException(
+ 'Window update received from remote peer would make flow control '
+ 'window too large.');
+ } else {
+ _peerWindow.modify(increment);
+ }
+
+ // If we transitioned from an negative/empty window to a positive window
+ // we'll fire an event that more data frames can be sent now.
+ if (positiveWindow.wouldBuffer && _peerWindow.size > 0) {
+ positiveWindow.markUnBuffered();
+ }
+ }
+
+ /// Update the peer window by subtracting [numberOfBytes].
+ ///
+ /// The remote peer will send us [WindowUpdateFrame]s which will increase
+ /// the window again at a later point in time.
+ void decreaseWindow(int numberOfBytes) {
+ _peerWindow.modify(-numberOfBytes);
+ if (_peerWindow.size <= 0) {
+ positiveWindow.markBuffered();
+ }
+ }
+}
+
+/// Handles the connection window for outgoing data frames.
+class OutgoingConnectionWindowHandler extends AbstractOutgoingWindowHandler {
+ OutgoingConnectionWindowHandler(Window window) : super(window);
+}
+
+/// Handles the window for outgoing messages to the peer.
+class OutgoingStreamWindowHandler extends AbstractOutgoingWindowHandler {
+ OutgoingStreamWindowHandler(Window window) : super(window);
+
+ /// Update the peer window by adding [difference] to it.
+ ///
+ ///
+ /// The remote peer has send a new [SettingsFrame] which updated the default
+ /// stream level [Setting.SETTINGS_INITIAL_WINDOW_SIZE]. This causes all
+ /// existing streams to update the flow stream-level flow control window.
+ void processInitialWindowSizeSettingChange(int difference) {
+ if ((_peerWindow.size + difference) > Window.MAX_WINDOW_SIZE) {
+ throw new FlowControlException(
+ 'Window update received from remote peer would make flow control '
+ 'window too large.');
+ } else {
+ _peerWindow.modify(difference);
+ if (_peerWindow.size <= 0) {
+ positiveWindow.markBuffered();
+ } else if (positiveWindow.wouldBuffer) {
+ positiveWindow.markUnBuffered();
+ }
+ }
+ }
+}
+
+/// Mirrors the flow control window the remote end is using.
+class IncomingWindowHandler {
+ /// The [FrameWriter] used for writing [WindowUpdateFrame]s to the wire.
+ final FrameWriter _frameWriter;
+
+ /// The mirror of the [Window] the remote end sees.
+ ///
+ /// If [_localWindow ] turns negative, it means the remote peer sent us more
+ /// data than we allowed it to send.
+ final Window _localWindow;
+
+ /// The stream id this window handler is for (is `0` for connection level).
+ final int _streamId;
+
+ IncomingWindowHandler.stream(
+ this._frameWriter, this._localWindow, this._streamId);
+
+ IncomingWindowHandler.connection(this._frameWriter, this._localWindow)
+ : _streamId = 0;
+
+ /// The current size for the incoming data window.
+ ///
+ /// (This should never get negative, otherwise the peer send us more data
+ /// than we told it to send.)
+ int get localWindowSize => _localWindow.size;
+
+ /// Signals that we received [numberOfBytes] from the remote peer.
+ void gotData(int numberOfBytes) {
+ _localWindow.modify(-numberOfBytes);
+
+ // If this turns negative, it means the remote end send us more data
+ // then we announced we can handle (i.e. the remote window size must be
+ // negative).
+ //
+ // NOTE: [_localWindow.size] tracks the amount of data we advertised that we
+ // can handle. The value can change in three situations:
+ //
+ // a) We received data from the remote end (we can handle now less data)
+ // => This is handled by [gotData].
+ //
+ // b) We processed data from the remote end (we can handle now more data)
+ // => This is handled by [dataProcessed].
+ //
+ // c) We increase/decrease the initial stream window size after the
+ // stream was created (newer streams will start with the changed
+ // initial stream window size).
+ // => This is not an issue, because we don't support changing the
+ // initial window size later on -- only during the initial
+ // settings exchange. Since streams (and therefore instances
+ // of [IncomingWindowHandler]) are only created after sending out
+ // our initial settings.
+ //
+ if (_localWindow.size < 0) {
+ throw new FlowControlException(
+ 'Connection level flow control window became negative.');
+ }
+ }
+
+ /// Tell the peer we received [numberOfBytes] bytes. It will increase it's
+ /// sending window then.
+ ///
+ // TODO/FIXME: If we pause and don't want to get more data, we have to
+ // - either stop sending window update frames
+ // - or decreasing the window size
+ void dataProcessed(int numberOfBytes) {
+ _localWindow.modify(numberOfBytes);
+
+ // TODO: This can be optimized by delaying the window update to
+ // send one update with a bigger difference than multiple small update
+ // frames.
+ _frameWriter.writeWindowUpdate(numberOfBytes, streamId: _streamId);
+ }
+}
diff --git a/http2/lib/src/frames/frame_defragmenter.dart b/http2/lib/src/frames/frame_defragmenter.dart
new file mode 100644
index 0000000..44fc076
--- /dev/null
+++ b/http2/lib/src/frames/frame_defragmenter.dart
@@ -0,0 +1,93 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library http2.src.frames.frame_defragmenter;
+
+import '../sync_errors.dart';
+
+import 'frames.dart';
+
+/// Class used for defragmenting [HeadersFrame]s and [PushPromiseFrame]s.
+// TODO: Somehow emit an error if too many continuation frames have been sent
+// (since we're buffering all of them).
+class FrameDefragmenter {
+ /// The current incomplete [HeadersFrame] fragment.
+ HeadersFrame _headersFrame;
+
+ /// The current incomplete [PushPromiseFrame] fragment.
+ PushPromiseFrame _pushPromiseFrame;
+
+ /// Tries to defragment [frame].
+ ///
+ /// If the given [frame] is a [HeadersFrame] or a [PushPromiseFrame] which
+ /// needs de-fragmentation, it will be saved and `null` will be returned.
+ ///
+ /// If there is currently an incomplete [HeadersFrame] or [PushPromiseFrame]
+ /// saved, [frame] needs to be a [ContinuationFrame]. It will be added to the
+ /// saved frame. In case the defragmentation is complete, the defragmented
+ /// [HeadersFrame] or [PushPromiseFrame] will be returned.
+ ///
+ /// All other [Frame] types will be returned.
+ // TODO: Consider handling continuation frames without preceding
+ // headers/push-promise frame here instead of the call site?
+ Frame tryDefragmentFrame(Frame frame) {
+ if (_headersFrame != null) {
+ if (frame is ContinuationFrame) {
+ if (_headersFrame.header.streamId != frame.header.streamId) {
+ throw new ProtocolException(
+ 'Defragmentation: frames have different stream ids.');
+ }
+ _headersFrame = _headersFrame.addBlockContinuation(frame);
+
+ if (frame.hasEndHeadersFlag) {
+ var frame = _headersFrame;
+ _headersFrame = null;
+ return frame;
+ } else {
+ return null;
+ }
+ } else {
+ throw new ProtocolException(
+ 'Defragmentation: Incomplete frame must be followed by '
+ 'continuation frame.');
+ }
+ } else if (_pushPromiseFrame != null) {
+ if (frame is ContinuationFrame) {
+ if (_pushPromiseFrame.header.streamId != frame.header.streamId) {
+ throw new ProtocolException(
+ 'Defragmentation: frames have different stream ids.');
+ }
+ _pushPromiseFrame = _pushPromiseFrame.addBlockContinuation(frame);
+
+ if (frame.hasEndHeadersFlag) {
+ var frame = _pushPromiseFrame;
+ _pushPromiseFrame = null;
+ return frame;
+ } else {
+ return null;
+ }
+ } else {
+ throw new ProtocolException(
+ 'Defragmentation: Incomplete frame must be followed by '
+ 'continuation frame.');
+ }
+ } else {
+ if (frame is HeadersFrame) {
+ if (!frame.hasEndHeadersFlag) {
+ _headersFrame = frame;
+ return null;
+ }
+ } else if (frame is PushPromiseFrame) {
+ if (!frame.hasEndHeadersFlag) {
+ _pushPromiseFrame = frame;
+ return null;
+ }
+ }
+ }
+
+ // If this frame is not relevant for header defragmentation, we pass it to
+ // the next stage.
+ return frame;
+ }
+}
diff --git a/http2/lib/src/frames/frame_reader.dart b/http2/lib/src/frames/frame_reader.dart
new file mode 100644
index 0000000..2812dc6
--- /dev/null
+++ b/http2/lib/src/frames/frame_reader.dart
@@ -0,0 +1,284 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of http2.src.frames;
+
+/// Used for converting a `Stream<List<int>>` to a `Stream<Frame>`.
+class FrameReader {
+ final Stream<List<int>> _inputStream;
+
+ /// Connection settings which this reader needs to ensure the remote end is
+ /// complying with.
+ ActiveSettings _localSettings;
+
+ StreamSubscription<List<int>> _subscription;
+ StreamController<Frame> _framesController;
+
+ FrameReader(this._inputStream, this._localSettings);
+
+ /// Starts to listen on the input stream and decodes HTTP/2 transport frames.
+ Stream<Frame> startDecoding() {
+ List<List<int>> bufferedData = new List<List<int>>();
+ int bufferedLength = 0;
+
+ FrameHeader tryReadHeader() {
+ if (bufferedLength >= FRAME_HEADER_SIZE) {
+ // Get at least FRAME_HEADER_SIZE bytes in the first byte array.
+ _mergeLists(bufferedData, FRAME_HEADER_SIZE);
+
+ // Read the frame header from the first byte array.
+ return _readFrameHeader(bufferedData[0], 0);
+ }
+ return null;
+ }
+
+ Frame tryReadFrame(FrameHeader header) {
+ int totalFrameLen = FRAME_HEADER_SIZE + header.length;
+ if (bufferedLength >= totalFrameLen) {
+ // Get the whole frame in the first byte array.
+ _mergeLists(bufferedData, totalFrameLen);
+
+ // Read the frame.
+ Frame frame = _readFrame(header, bufferedData[0], FRAME_HEADER_SIZE);
+
+ // Update bufferedData/bufferedLength
+ int firstChunkLen = bufferedData[0].length;
+ if (firstChunkLen == totalFrameLen) {
+ bufferedData.removeAt(0);
+ } else {
+ bufferedData[0] = viewOrSublist(
+ bufferedData[0], totalFrameLen, firstChunkLen - totalFrameLen);
+ }
+ bufferedLength -= totalFrameLen;
+
+ return frame;
+ }
+ return null;
+ }
+
+ _framesController = new StreamController(
+ onListen: () {
+ FrameHeader header;
+
+ void terminateWithError(error, [StackTrace stack]) {
+ header = null;
+ _framesController.addError(error, stack);
+ _subscription.cancel();
+ _framesController.close();
+ }
+
+ _subscription = _inputStream.listen((List<int> data) {
+ bufferedData.add(data);
+ bufferedLength += data.length;
+
+ try {
+ while (true) {
+ if (header == null) {
+ header = tryReadHeader();
+ }
+ if (header != null) {
+ if (header.length > _localSettings.maxFrameSize) {
+ terminateWithError(
+ new FrameSizeException('Incoming frame is too big.'));
+ return;
+ }
+
+ Frame frame = tryReadFrame(header);
+
+ if (frame != null) {
+ _framesController.add(frame);
+ header = null;
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ } catch (error, stack) {
+ terminateWithError(error, stack);
+ }
+ }, onError: (error, StackTrace stack) {
+ terminateWithError(error, stack);
+ }, onDone: () {
+ if (bufferedLength == 0) {
+ _framesController.close();
+ } else {
+ terminateWithError(new FrameSizeException(
+ 'Incoming byte stream ended with incomplete frame'));
+ }
+ });
+ },
+ onPause: () => _subscription.pause(),
+ onResume: () => _subscription.resume());
+
+ return _framesController.stream;
+ }
+
+ /// Combine combines/merges `List<int>`s of `bufferedData` until
+ /// `numberOfBytes` have been accumulated.
+ ///
+ /// After calling `mergeLists`, `bufferedData[0]` will contain at least
+ /// `numberOfBytes` bytes.
+ void _mergeLists(List<List<int>> bufferedData, int numberOfBytes) {
+ if (bufferedData[0].length < numberOfBytes) {
+ int numLists = 0;
+ int accumulatedLength = 0;
+ while (accumulatedLength < numberOfBytes &&
+ numLists <= bufferedData.length) {
+ accumulatedLength += bufferedData[numLists++].length;
+ }
+ assert(accumulatedLength >= numberOfBytes);
+ var newList = new Uint8List(accumulatedLength);
+ int offset = 0;
+ for (int i = 0; i < numLists; i++) {
+ List<int> data = bufferedData[i];
+ newList.setRange(offset, offset + data.length, data);
+ offset += data.length;
+ }
+ bufferedData[0] = newList;
+ bufferedData.removeRange(1, numLists);
+ }
+ }
+
+ /// Reads a FrameHeader] from [bytes], starting at [offset].
+ FrameHeader _readFrameHeader(List<int> bytes, int offset) {
+ int length = readInt24(bytes, offset);
+ int type = bytes[offset + 3];
+ int flags = bytes[offset + 4];
+ int streamId = readInt32(bytes, offset + 5) & 0x7fffffff;
+
+ return new FrameHeader(length, type, flags, streamId);
+ }
+
+ /// Reads a [Frame] from [bytes], starting at [frameOffset].
+ Frame _readFrame(FrameHeader header, List<int> bytes, int frameOffset) {
+ int frameEnd = frameOffset + header.length;
+
+ int offset = frameOffset;
+ switch (header.type) {
+ case FrameType.DATA:
+ int padLength = 0;
+ if (_isFlagSet(header.flags, DataFrame.FLAG_PADDED)) {
+ _checkFrameLengthCondition((frameEnd - offset) >= 1);
+ padLength = bytes[offset++];
+ }
+ int dataLen = frameEnd - offset - padLength;
+ _checkFrameLengthCondition(dataLen >= 0);
+ var dataBytes = viewOrSublist(bytes, offset, dataLen);
+ return new DataFrame(header, padLength, dataBytes);
+
+ case FrameType.HEADERS:
+ int padLength = 0;
+ if (_isFlagSet(header.flags, HeadersFrame.FLAG_PADDED)) {
+ _checkFrameLengthCondition((frameEnd - offset) >= 1);
+ padLength = bytes[offset++];
+ }
+ int streamDependency;
+ bool exclusiveDependency = false;
+ int weight;
+ if (_isFlagSet(header.flags, HeadersFrame.FLAG_PRIORITY)) {
+ _checkFrameLengthCondition((frameEnd - offset) >= 5);
+ exclusiveDependency = (bytes[offset] & 0x80) == 0x80;
+ streamDependency = readInt32(bytes, offset) & 0x7fffffff;
+ offset += 4;
+ weight = bytes[offset++];
+ }
+ int headerBlockLen = frameEnd - offset - padLength;
+ _checkFrameLengthCondition(headerBlockLen >= 0);
+ var headerBlockFragment = viewOrSublist(bytes, offset, headerBlockLen);
+ return new HeadersFrame(header, padLength, exclusiveDependency,
+ streamDependency, weight, headerBlockFragment);
+
+ case FrameType.PRIORITY:
+ _checkFrameLengthCondition(
+ (frameEnd - offset) == PriorityFrame.FIXED_FRAME_LENGTH,
+ message: 'Priority frame length must be exactly 5 bytes.');
+ bool exclusiveDependency = (bytes[offset] & 0x80) == 0x80;
+ int streamDependency = readInt32(bytes, offset) & 0x7fffffff;
+ int weight = bytes[offset + 4];
+ return new PriorityFrame(
+ header, exclusiveDependency, streamDependency, weight);
+
+ case FrameType.RST_STREAM:
+ _checkFrameLengthCondition(
+ (frameEnd - offset) == RstStreamFrame.FIXED_FRAME_LENGTH,
+ message: 'Rst frames must have a length of 4.');
+ int errorCode = readInt32(bytes, offset);
+ return new RstStreamFrame(header, errorCode);
+
+ case FrameType.SETTINGS:
+ _checkFrameLengthCondition((header.length % 6) == 0,
+ message: 'Settings frame length must be a multiple of 6 bytes.');
+
+ int count = header.length ~/ 6;
+ var settings = new List<Setting>(count);
+ for (int i = 0; i < count; i++) {
+ int identifier = readInt16(bytes, offset + 6 * i);
+ int value = readInt32(bytes, offset + 6 * i + 2);
+ settings[i] = new Setting(identifier, value);
+ }
+ var frame = new SettingsFrame(header, settings);
+ if (frame.hasAckFlag) {
+ _checkFrameLengthCondition(header.length == 0,
+ message: 'Settings frame length must 0 for ACKs.');
+ }
+ return frame;
+
+ case FrameType.PUSH_PROMISE:
+ int padLength = 0;
+ if (_isFlagSet(header.flags, PushPromiseFrame.FLAG_PADDED)) {
+ _checkFrameLengthCondition((frameEnd - offset) >= 1);
+ padLength = bytes[offset++];
+ }
+ int promisedStreamId = readInt32(bytes, offset) & 0x7fffffff;
+ offset += 4;
+ int headerBlockLen = frameEnd - offset - padLength;
+ _checkFrameLengthCondition(headerBlockLen >= 0);
+ var headerBlockFragment = viewOrSublist(bytes, offset, headerBlockLen);
+ return new PushPromiseFrame(
+ header, padLength, promisedStreamId, headerBlockFragment);
+
+ case FrameType.PING:
+ _checkFrameLengthCondition(
+ (frameEnd - offset) == PingFrame.FIXED_FRAME_LENGTH,
+ message: 'Ping frames must have a length of 8.');
+ var opaqueData = readInt64(bytes, offset);
+ return new PingFrame(header, opaqueData);
+
+ case FrameType.GOAWAY:
+ _checkFrameLengthCondition((frameEnd - offset) >= 8);
+ int lastStreamId = readInt32(bytes, offset);
+ int errorCode = readInt32(bytes, offset + 4);
+ var debugData = viewOrSublist(bytes, offset + 8, header.length - 8);
+ return new GoawayFrame(header, lastStreamId, errorCode, debugData);
+
+ case FrameType.WINDOW_UPDATE:
+ _checkFrameLengthCondition(
+ (frameEnd - offset) == WindowUpdateFrame.FIXED_FRAME_LENGTH,
+ message: 'Window update frames must have a length of 4.');
+ int windowSizeIncrement = readInt32(bytes, offset) & 0x7fffffff;
+ return new WindowUpdateFrame(header, windowSizeIncrement);
+
+ case FrameType.CONTINUATION:
+ var headerBlockFragment =
+ viewOrSublist(bytes, offset, frameEnd - offset);
+ return new ContinuationFrame(header, headerBlockFragment);
+
+ default:
+ // Unknown frames should be ignored according to spec.
+ return new UnknownFrame(
+ header, viewOrSublist(bytes, offset, frameEnd - offset));
+ }
+ }
+
+ /// Checks that [condition] is `true` and raises an [FrameSizeException]
+ /// otherwise.
+ void _checkFrameLengthCondition(bool condition,
+ {String message: 'Frame not long enough.'}) {
+ if (!condition) {
+ throw new FrameSizeException(message);
+ }
+ }
+}
diff --git a/http2/lib/src/frames/frame_types.dart b/http2/lib/src/frames/frame_types.dart
new file mode 100644
index 0000000..205f2a8
--- /dev/null
+++ b/http2/lib/src/frames/frame_types.dart
@@ -0,0 +1,330 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of http2.src.frames;
+
+const int FRAME_HEADER_SIZE = 9;
+
+class FrameType {
+ static const int DATA = 0;
+ static const int HEADERS = 1;
+ static const int PRIORITY = 2;
+ static const int RST_STREAM = 3;
+ static const int SETTINGS = 4;
+ static const int PUSH_PROMISE = 5;
+ static const int PING = 6;
+ static const int GOAWAY = 7;
+ static const int WINDOW_UPDATE = 8;
+ static const int CONTINUATION = 9;
+}
+
+class ErrorCode {
+ static const int NO_ERROR = 0;
+ static const int PROTOCOL_ERROR = 1;
+ static const int INTERNAL_ERROR = 2;
+ static const int FLOW_CONTROL_ERROR = 3;
+ static const int SETTINGS_TIMEOUT = 4;
+ static const int STREAM_CLOSED = 5;
+ static const int FRAME_SIZE_ERROR = 6;
+ static const int REFUSED_STREAM = 7;
+ static const int CANCEL = 8;
+ static const int COMPRESSION_ERROR = 9;
+ static const int CONNECT_ERROR = 10;
+ static const int ENHANCE_YOUR_CALM = 11;
+ static const int INADEQUATE_SECURITY = 12;
+ static const int HTTP_1_1_REQUIRED = 13;
+}
+
+class FrameHeader {
+ final int length;
+ final int type;
+ final int flags;
+ final int streamId;
+
+ FrameHeader(this.length, this.type, this.flags, this.streamId);
+
+ Map toJson() =>
+ {'length': length, 'type': type, 'flags': flags, 'streamId': streamId};
+}
+
+class Frame {
+ static const int MAX_LEN = (1 << 24) - 1;
+
+ final FrameHeader header;
+
+ Frame(this.header);
+
+ Map toJson() => {'header': header.toJson()};
+}
+
+class DataFrame extends Frame {
+ static const int FLAG_END_STREAM = 0x1;
+ static const int FLAG_PADDED = 0x8;
+
+ /// The number of padding bytes.
+ final int padLength;
+
+ final List<int> bytes;
+
+ DataFrame(FrameHeader header, this.padLength, this.bytes) : super(header);
+
+ bool get hasEndStreamFlag => _isFlagSet(header.flags, FLAG_END_STREAM);
+ bool get hasPaddedFlag => _isFlagSet(header.flags, FLAG_PADDED);
+
+ Map toJson() => super.toJson()
+ ..addAll({
+ 'padLength': padLength,
+ 'bytes (length)': bytes.length,
+ 'bytes (up to 4 bytes)': bytes.length > 4 ? bytes.sublist(0, 4) : bytes,
+ });
+}
+
+class HeadersFrame extends Frame {
+ static const int FLAG_END_STREAM = 0x1;
+ static const int FLAG_END_HEADERS = 0x4;
+ static const int FLAG_PADDED = 0x8;
+ static const int FLAG_PRIORITY = 0x20;
+
+ // NOTE: This is the size a [HeadersFrame] can have in addition to padding
+ // and header block fragment data.
+ static const int MAX_CONSTANT_PAYLOAD = 6;
+
+ /// The number of padding bytes (might be null).
+ final int padLength;
+
+ final bool exclusiveDependency;
+ final int streamDependency;
+ final int weight;
+ final List<int> headerBlockFragment;
+
+ HeadersFrame(FrameHeader header, this.padLength, this.exclusiveDependency,
+ this.streamDependency, this.weight, this.headerBlockFragment)
+ : super(header);
+
+ /// This will be set from the outside after decoding.
+ List<Header> decodedHeaders;
+
+ bool get hasEndStreamFlag => _isFlagSet(header.flags, FLAG_END_STREAM);
+ bool get hasEndHeadersFlag => _isFlagSet(header.flags, FLAG_END_HEADERS);
+ bool get hasPaddedFlag => _isFlagSet(header.flags, FLAG_PADDED);
+ bool get hasPriorityFlag => _isFlagSet(header.flags, FLAG_PRIORITY);
+
+ HeadersFrame addBlockContinuation(ContinuationFrame frame) {
+ var fragment = frame.headerBlockFragment;
+ var flags = header.flags | frame.header.flags;
+ var fh = new FrameHeader(
+ header.length + fragment.length, header.type, flags, header.streamId);
+
+ var mergedHeaderBlockFragment =
+ new Uint8List(headerBlockFragment.length + fragment.length);
+
+ mergedHeaderBlockFragment.setRange(
+ 0, headerBlockFragment.length, headerBlockFragment);
+
+ mergedHeaderBlockFragment.setRange(
+ headerBlockFragment.length, mergedHeaderBlockFragment.length, fragment);
+
+ return new HeadersFrame(fh, padLength, exclusiveDependency,
+ streamDependency, weight, mergedHeaderBlockFragment);
+ }
+
+ Map toJson() => super.toJson()
+ ..addAll({
+ 'padLength': padLength,
+ 'exclusiveDependency': exclusiveDependency,
+ 'streamDependency': streamDependency,
+ 'weight': weight,
+ 'headerBlockFragment (length)': headerBlockFragment.length
+ });
+}
+
+class PriorityFrame extends Frame {
+ static const int FIXED_FRAME_LENGTH = 5;
+
+ final bool exclusiveDependency;
+ final int streamDependency;
+ final int weight;
+
+ PriorityFrame(FrameHeader header, this.exclusiveDependency,
+ this.streamDependency, this.weight)
+ : super(header);
+
+ Map toJson() => super.toJson()
+ ..addAll({
+ 'exclusiveDependency': exclusiveDependency,
+ 'streamDependency': streamDependency,
+ 'weight': weight,
+ });
+}
+
+class RstStreamFrame extends Frame {
+ static const int FIXED_FRAME_LENGTH = 4;
+
+ final int errorCode;
+
+ RstStreamFrame(FrameHeader header, this.errorCode) : super(header);
+
+ Map toJson() => super.toJson()
+ ..addAll({
+ 'errorCode': errorCode,
+ });
+}
+
+class Setting {
+ static const int SETTINGS_HEADER_TABLE_SIZE = 1;
+ static const int SETTINGS_ENABLE_PUSH = 2;
+ static const int SETTINGS_MAX_CONCURRENT_STREAMS = 3;
+ static const int SETTINGS_INITIAL_WINDOW_SIZE = 4;
+ static const int SETTINGS_MAX_FRAME_SIZE = 5;
+ static const int SETTINGS_MAX_HEADER_LIST_SIZE = 6;
+
+ final int identifier;
+ final int value;
+
+ Setting(this.identifier, this.value);
+
+ Map toJson() => {'identifier': identifier, 'value': value};
+}
+
+class SettingsFrame extends Frame {
+ static const int FLAG_ACK = 0x1;
+
+ // A setting consist of a 2 byte identifier and a 4 byte value.
+ static const int SETTING_SIZE = 6;
+
+ final List<Setting> settings;
+
+ SettingsFrame(FrameHeader header, this.settings) : super(header);
+
+ bool get hasAckFlag => _isFlagSet(header.flags, FLAG_ACK);
+
+ Map toJson() => super.toJson()
+ ..addAll({
+ 'settings': settings.map((s) => s.toJson()).toList(),
+ });
+}
+
+class PushPromiseFrame extends Frame {
+ static const int FLAG_END_HEADERS = 0x4;
+ static const int FLAG_PADDED = 0x8;
+
+ // NOTE: This is the size a [PushPromiseFrame] can have in addition to padding
+ // and header block fragment data.
+ static const int MAX_CONSTANT_PAYLOAD = 5;
+
+ final int padLength;
+ final int promisedStreamId;
+ final List<int> headerBlockFragment;
+
+ /// This will be set from the outside after decoding.
+ List<Header> decodedHeaders;
+
+ PushPromiseFrame(FrameHeader header, this.padLength, this.promisedStreamId,
+ this.headerBlockFragment)
+ : super(header);
+
+ bool get hasEndHeadersFlag => _isFlagSet(header.flags, FLAG_END_HEADERS);
+ bool get hasPaddedFlag => _isFlagSet(header.flags, FLAG_PADDED);
+
+ PushPromiseFrame addBlockContinuation(ContinuationFrame frame) {
+ var fragment = frame.headerBlockFragment;
+ var flags = header.flags | frame.header.flags;
+ var fh = new FrameHeader(
+ header.length + fragment.length, header.type, flags, header.streamId);
+
+ var mergedHeaderBlockFragment =
+ new Uint8List(headerBlockFragment.length + fragment.length);
+
+ mergedHeaderBlockFragment.setRange(
+ 0, headerBlockFragment.length, headerBlockFragment);
+
+ mergedHeaderBlockFragment.setRange(
+ headerBlockFragment.length, mergedHeaderBlockFragment.length, fragment);
+
+ return new PushPromiseFrame(
+ fh, padLength, promisedStreamId, mergedHeaderBlockFragment);
+ }
+
+ Map toJson() => super.toJson()
+ ..addAll({
+ 'padLength': padLength,
+ 'promisedStreamId': promisedStreamId,
+ 'headerBlockFragment (len)': headerBlockFragment.length,
+ });
+}
+
+class PingFrame extends Frame {
+ static const int FIXED_FRAME_LENGTH = 8;
+
+ static const int FLAG_ACK = 0x1;
+
+ final int opaqueData;
+
+ PingFrame(FrameHeader header, this.opaqueData) : super(header);
+
+ bool get hasAckFlag => _isFlagSet(header.flags, FLAG_ACK);
+
+ Map toJson() => super.toJson()
+ ..addAll({
+ 'opaqueData': opaqueData,
+ });
+}
+
+class GoawayFrame extends Frame {
+ final int lastStreamId;
+ final int errorCode;
+ final List<int> debugData;
+
+ GoawayFrame(
+ FrameHeader header, this.lastStreamId, this.errorCode, this.debugData)
+ : super(header);
+
+ Map toJson() => super.toJson()
+ ..addAll({
+ 'lastStreamId': lastStreamId,
+ 'errorCode': errorCode,
+ 'debugData (length)': debugData.length,
+ });
+}
+
+class WindowUpdateFrame extends Frame {
+ static const int FIXED_FRAME_LENGTH = 4;
+
+ final int windowSizeIncrement;
+
+ WindowUpdateFrame(FrameHeader header, this.windowSizeIncrement)
+ : super(header);
+
+ Map toJson() => super.toJson()
+ ..addAll({
+ 'windowSizeIncrement': windowSizeIncrement,
+ });
+}
+
+class ContinuationFrame extends Frame {
+ static const int FLAG_END_HEADERS = 0x4;
+
+ final List<int> headerBlockFragment;
+
+ ContinuationFrame(FrameHeader header, this.headerBlockFragment)
+ : super(header);
+
+ bool get hasEndHeadersFlag => _isFlagSet(header.flags, FLAG_END_HEADERS);
+
+ Map toJson() => super.toJson()
+ ..addAll({
+ 'headerBlockFragment (length)': headerBlockFragment.length,
+ });
+}
+
+class UnknownFrame extends Frame {
+ final List<int> data;
+
+ UnknownFrame(FrameHeader header, this.data) : super(header);
+
+ Map toJson() => super.toJson()
+ ..addAll({
+ 'data (length)': data.length,
+ });
+}
diff --git a/http2/lib/src/frames/frame_utils.dart b/http2/lib/src/frames/frame_utils.dart
new file mode 100644
index 0000000..73a3173
--- /dev/null
+++ b/http2/lib/src/frames/frame_utils.dart
@@ -0,0 +1,7 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of http2.src.frames;
+
+bool _isFlagSet(int value, int flag) => value & flag == flag;
diff --git a/http2/lib/src/frames/frame_writer.dart b/http2/lib/src/frames/frame_writer.dart
new file mode 100644
index 0000000..6c79581
--- /dev/null
+++ b/http2/lib/src/frames/frame_writer.dart
@@ -0,0 +1,293 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+part of http2.src.frames;
+
+// TODO: No support for writing padded information.
+// TODO: No support for stream priorities.
+class FrameWriter {
+ /// The HPack compression context.
+ final HPackEncoder _hpackEncoder;
+
+ /// A buffered writer for outgoing bytes.
+ final BufferedBytesWriter _outWriter;
+
+ /// Connection settings which this writer needs to respect.
+ final ActiveSettings _peerSettings;
+
+ /// This is the maximum over all stream id's we've written to the underlying
+ /// sink.
+ int _highestWrittenStreamId = 0;
+
+ FrameWriter(
+ this._hpackEncoder, StreamSink<List<int>> outgoing, this._peerSettings)
+ : _outWriter = new BufferedBytesWriter(outgoing);
+
+ /// A indicator whether writes would be buffered.
+ BufferIndicator get bufferIndicator => _outWriter.bufferIndicator;
+
+ /// This is the maximum over all stream id's we've written to the underlying
+ /// sink.
+ int get highestWrittenStreamId => _highestWrittenStreamId;
+
+ void writeDataFrame(int streamId, List<int> data, {bool endStream: false}) {
+ while (data.length > _peerSettings.maxFrameSize) {
+ var chunk = viewOrSublist(data, 0, _peerSettings.maxFrameSize);
+ data = viewOrSublist(data, _peerSettings.maxFrameSize,
+ data.length - _peerSettings.maxFrameSize);
+ _writeDataFrameNoFragment(streamId, chunk, false);
+ }
+ _writeDataFrameNoFragment(streamId, data, endStream);
+ }
+
+ void _writeDataFrameNoFragment(int streamId, List<int> data, bool endStream) {
+ int type = FrameType.DATA;
+ int flags = endStream ? DataFrame.FLAG_END_STREAM : 0;
+
+ var buffer = new Uint8List(FRAME_HEADER_SIZE + data.length);
+ int offset = 0;
+
+ _setFrameHeader(buffer, offset, type, flags, streamId, data.length);
+ offset += FRAME_HEADER_SIZE;
+
+ buffer.setRange(offset, offset + data.length, data);
+
+ _writeData(buffer);
+ }
+
+ void writeHeadersFrame(int streamId, List<Header> headers,
+ {bool endStream: true}) {
+ var fragment = _hpackEncoder.encode(headers);
+ var maxSize =
+ _peerSettings.maxFrameSize - HeadersFrame.MAX_CONSTANT_PAYLOAD;
+
+ if (fragment.length < maxSize) {
+ _writeHeadersFrameNoFragment(streamId, fragment, true, endStream);
+ } else {
+ var chunk = fragment.sublist(0, maxSize);
+ fragment = fragment.sublist(maxSize);
+ _writeHeadersFrameNoFragment(streamId, chunk, false, endStream);
+ while (fragment.length > _peerSettings.maxFrameSize) {
+ var chunk = fragment.sublist(0, _peerSettings.maxFrameSize);
+ fragment = fragment.sublist(_peerSettings.maxFrameSize);
+ _writeContinuationFrame(streamId, chunk, false);
+ }
+ _writeContinuationFrame(streamId, fragment, true);
+ }
+ }
+
+ void _writeHeadersFrameNoFragment(
+ int streamId, List<int> fragment, bool endHeaders, bool endStream) {
+ int type = FrameType.HEADERS;
+ int flags = 0;
+ if (endHeaders) flags |= HeadersFrame.FLAG_END_HEADERS;
+ if (endStream) flags |= HeadersFrame.FLAG_END_STREAM;
+
+ var buffer = new Uint8List(FRAME_HEADER_SIZE + fragment.length);
+ int offset = 0;
+
+ _setFrameHeader(buffer, offset, type, flags, streamId, fragment.length);
+ offset += FRAME_HEADER_SIZE;
+
+ buffer.setRange(offset, buffer.length, fragment);
+
+ _writeData(buffer);
+ }
+
+ void _writeContinuationFrame(
+ int streamId, List<int> fragment, bool endHeaders) {
+ int type = FrameType.CONTINUATION;
+ int flags = endHeaders ? ContinuationFrame.FLAG_END_HEADERS : 0;
+
+ var buffer = new Uint8List(FRAME_HEADER_SIZE + fragment.length);
+ int offset = 0;
+
+ _setFrameHeader(buffer, offset, type, flags, streamId, fragment.length);
+ offset += FRAME_HEADER_SIZE;
+
+ buffer.setRange(offset, buffer.length, fragment);
+
+ _writeData(buffer);
+ }
+
+ void writePriorityFrame(int streamId, int streamDependency, int weight,
+ {bool exclusive: false}) {
+ int type = FrameType.PRIORITY;
+ int flags = 0;
+
+ var buffer =
+ new Uint8List(FRAME_HEADER_SIZE + PriorityFrame.FIXED_FRAME_LENGTH);
+ int offset = 0;
+
+ _setFrameHeader(buffer, offset, type, flags, streamId, 5);
+ offset += FRAME_HEADER_SIZE;
+
+ if (exclusive) {
+ setInt32(buffer, offset, (1 << 31) | streamDependency);
+ } else {
+ setInt32(buffer, offset, streamDependency);
+ }
+ buffer[offset + 4] = weight;
+
+ _writeData(buffer);
+ }
+
+ void writeRstStreamFrame(int streamId, int errorCode) {
+ int type = FrameType.RST_STREAM;
+ int flags = 0;
+
+ var buffer =
+ new Uint8List(FRAME_HEADER_SIZE + RstStreamFrame.FIXED_FRAME_LENGTH);
+ int offset = 0;
+
+ _setFrameHeader(buffer, offset, type, flags, streamId, 4);
+ offset += FRAME_HEADER_SIZE;
+
+ setInt32(buffer, offset, errorCode);
+
+ _writeData(buffer);
+ }
+
+ void writeSettingsFrame(List<Setting> settings) {
+ int type = FrameType.SETTINGS;
+ int flags = 0;
+
+ var buffer = new Uint8List(FRAME_HEADER_SIZE + 6 * settings.length);
+ int offset = 0;
+
+ _setFrameHeader(buffer, offset, type, flags, 0, 6 * settings.length);
+ offset += FRAME_HEADER_SIZE;
+
+ for (int i = 0; i < settings.length; i++) {
+ var setting = settings[i];
+ setInt16(buffer, offset + 6 * i, setting.identifier);
+ setInt32(buffer, offset + 6 * i + 2, setting.value);
+ }
+
+ _writeData(buffer);
+ }
+
+ void writeSettingsAckFrame() {
+ int type = FrameType.SETTINGS;
+ int flags = SettingsFrame.FLAG_ACK;
+
+ var buffer = new Uint8List(FRAME_HEADER_SIZE);
+ int offset = 0;
+
+ _setFrameHeader(buffer, offset, type, flags, 0, 0);
+ offset += FRAME_HEADER_SIZE;
+
+ _writeData(buffer);
+ }
+
+ void writePushPromiseFrame(
+ int streamId, int promisedStreamId, List<Header> headers) {
+ var fragment = _hpackEncoder.encode(headers);
+ var maxSize =
+ _peerSettings.maxFrameSize - PushPromiseFrame.MAX_CONSTANT_PAYLOAD;
+
+ if (fragment.length < maxSize) {
+ _writePushPromiseFrameNoFragmentation(
+ streamId, promisedStreamId, fragment, true);
+ } else {
+ var chunk = fragment.sublist(0, maxSize);
+ fragment = fragment.sublist(maxSize);
+ _writePushPromiseFrameNoFragmentation(
+ streamId, promisedStreamId, chunk, false);
+ while (fragment.length > _peerSettings.maxFrameSize) {
+ var chunk = fragment.sublist(0, _peerSettings.maxFrameSize);
+ fragment = fragment.sublist(_peerSettings.maxFrameSize);
+ _writeContinuationFrame(streamId, chunk, false);
+ }
+ _writeContinuationFrame(streamId, chunk, true);
+ }
+ }
+
+ void _writePushPromiseFrameNoFragmentation(
+ int streamId, int promisedStreamId, List<int> fragment, bool endHeaders) {
+ int type = FrameType.PUSH_PROMISE;
+ int flags = endHeaders ? HeadersFrame.FLAG_END_HEADERS : 0;
+
+ var buffer = new Uint8List(FRAME_HEADER_SIZE + 4 + fragment.length);
+ int offset = 0;
+
+ _setFrameHeader(buffer, offset, type, flags, streamId, 4 + fragment.length);
+ offset += FRAME_HEADER_SIZE;
+
+ setInt32(buffer, offset, promisedStreamId);
+ buffer.setRange(offset + 4, offset + 4 + fragment.length, fragment);
+
+ _writeData(buffer);
+ }
+
+ void writePingFrame(int opaqueData, {bool ack: false}) {
+ int type = FrameType.PING;
+ int flags = ack ? PingFrame.FLAG_ACK : 0;
+
+ var buffer =
+ new Uint8List(FRAME_HEADER_SIZE + PingFrame.FIXED_FRAME_LENGTH);
+ int offset = 0;
+
+ _setFrameHeader(buffer, 0, type, flags, 0, 8);
+ offset += FRAME_HEADER_SIZE;
+
+ setInt64(buffer, offset, opaqueData);
+ _writeData(buffer);
+ }
+
+ void writeGoawayFrame(int lastStreamId, int errorCode, List<int> debugData) {
+ int type = FrameType.GOAWAY;
+ int flags = 0;
+
+ var buffer = new Uint8List(FRAME_HEADER_SIZE + 8 + debugData.length);
+ int offset = 0;
+
+ _setFrameHeader(buffer, offset, type, flags, 0, 8 + debugData.length);
+ offset += FRAME_HEADER_SIZE;
+
+ setInt32(buffer, offset, lastStreamId);
+ setInt32(buffer, offset + 4, errorCode);
+ buffer.setRange(offset + 8, buffer.length, debugData);
+
+ _writeData(buffer);
+ }
+
+ void writeWindowUpdate(int sizeIncrement, {int streamId: 0}) {
+ int type = FrameType.WINDOW_UPDATE;
+ int flags = 0;
+
+ var buffer =
+ new Uint8List(FRAME_HEADER_SIZE + WindowUpdateFrame.FIXED_FRAME_LENGTH);
+ int offset = 0;
+
+ _setFrameHeader(buffer, offset, type, flags, streamId, 4);
+ offset += FRAME_HEADER_SIZE;
+
+ setInt32(buffer, offset, sizeIncrement);
+
+ _writeData(buffer);
+ }
+
+ void _writeData(List<int> bytes) {
+ _outWriter.add(bytes);
+ }
+
+ /// Closes the underlying sink and returns [doneFuture].
+ Future close() {
+ return _outWriter.close().whenComplete(() => doneFuture);
+ }
+
+ /// The future which will complete once this writer is done.
+ Future get doneFuture => _outWriter.doneFuture;
+
+ void _setFrameHeader(List<int> bytes, int offset, int type, int flags,
+ int streamId, int length) {
+ setInt24(bytes, offset, length);
+ bytes[3] = type;
+ bytes[4] = flags;
+ setInt32(bytes, 5, streamId);
+
+ _highestWrittenStreamId = max(_highestWrittenStreamId, streamId);
+ }
+}
diff --git a/http2/lib/src/frames/frames.dart b/http2/lib/src/frames/frames.dart
new file mode 100644
index 0000000..8374eee
--- /dev/null
+++ b/http2/lib/src/frames/frames.dart
@@ -0,0 +1,20 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library http2.src.frames;
+
+import 'dart:async';
+import 'dart:math' show max;
+import 'dart:typed_data';
+
+import '../async_utils/async_utils.dart';
+import '../byte_utils.dart';
+import '../hpack/hpack.dart';
+import '../settings/settings.dart';
+import '../sync_errors.dart';
+
+part "frame_types.dart";
+part "frame_utils.dart";
+part "frame_reader.dart";
+part "frame_writer.dart";
diff --git a/http2/lib/src/hpack/hpack.dart b/http2/lib/src/hpack/hpack.dart
new file mode 100644
index 0000000..4821511
--- /dev/null
+++ b/http2/lib/src/hpack/hpack.dart
@@ -0,0 +1,349 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Implements a [HPackContext] for encoding/decoding headers according to the
+/// HPACK specificaiton. See here for more information:
+/// https://tools.ietf.org/html/draft-ietf-httpbis-header-compression-10
+library http2.hpack;
+
+import 'dart:convert' show ascii;
+import 'dart:io';
+
+import '../byte_utils.dart';
+
+import 'huffman.dart';
+import 'huffman_table.dart';
+
+/// Exception raised due to encoding/decoding errors.
+class HPackDecodingException implements Exception {
+ final String _message;
+
+ HPackDecodingException(this._message);
+
+ String toString() => 'HPackDecodingException: $_message';
+}
+
+/// A HPACK encoding/decoding context.
+///
+/// This is a statefull class, so encoding/decoding changes internal state.
+class HPackContext {
+ final HPackEncoder encoder = new HPackEncoder();
+ final HPackDecoder decoder = new HPackDecoder();
+
+ HPackContext(
+ {int maxSendingHeaderTableSize: 4096,
+ int maxReceivingHeaderTableSize: 4096}) {
+ encoder.updateMaxSendingHeaderTableSize(maxSendingHeaderTableSize);
+ decoder.updateMaxReceivingHeaderTableSize(maxReceivingHeaderTableSize);
+ }
+}
+
+/// A HTTP/2 header.
+class Header {
+ final List<int> name;
+ final List<int> value;
+ final bool neverIndexed;
+
+ Header(this.name, this.value, {this.neverIndexed: false});
+
+ factory Header.ascii(String name, String value) {
+ return new Header(ascii.encode(name), ascii.encode(value));
+ }
+}
+
+/// A stateful HPACK decoder.
+class HPackDecoder {
+ int _maxHeaderTableSize;
+
+ final IndexTable _table = new IndexTable();
+
+ void updateMaxReceivingHeaderTableSize(int newMaximumSize) {
+ _maxHeaderTableSize = newMaximumSize;
+ }
+
+ List<Header> decode(List<int> data) {
+ int offset = 0;
+
+ int readInteger(int prefixBits) {
+ assert(prefixBits <= 8 && prefixBits > 0);
+
+ var byte = data[offset++] & ((1 << prefixBits) - 1);
+
+ int integer;
+ if (byte == ((1 << prefixBits) - 1)) {
+ // Length encodeded.
+ integer = 0;
+ int shift = 0;
+ while (true) {
+ bool done = (data[offset] & 0x80) != 0x80;
+ integer += (data[offset++] & 0x7f) << shift;
+ shift += 7;
+ if (done) break;
+ }
+ integer += (1 << prefixBits) - 1;
+ } else {
+ // In place length.
+ integer = byte;
+ }
+
+ return integer;
+ }
+
+ List<int> readStringLiteral() {
+ bool isHuffmanEncoding = (data[offset] & 0x80) != 0;
+ int length = readInteger(7);
+
+ var sublist = viewOrSublist(data, offset, length);
+ offset += length;
+ if (isHuffmanEncoding) {
+ return http2HuffmanCodec.decode(sublist);
+ } else {
+ return sublist;
+ }
+ }
+
+ Header readHeaderFieldInternal(int index, {bool neverIndexed: false}) {
+ List<int> name, value;
+ if (index > 0) {
+ name = _table.lookup(index).name;
+ value = readStringLiteral();
+ } else {
+ name = readStringLiteral();
+ value = readStringLiteral();
+ }
+ return new Header(name, value, neverIndexed: neverIndexed);
+ }
+
+ try {
+ List<Header> headers = [];
+ while (offset < data.length) {
+ int byte = data[offset];
+ bool isIndexedField = (byte & 0x80) != 0;
+ bool isIncrementalIndexing = (byte & 0xc0) == 0x40;
+
+ bool isWithoutIndexing = (byte & 0xf0) == 0;
+ bool isNeverIndexing = (byte & 0xf0) == 0x10;
+ bool isDynamicTableSizeUpdate = (byte & 0xe0) == 0x20;
+
+ if (isIndexedField) {
+ int index = readInteger(7);
+ var field = _table.lookup(index);
+ headers.add(field);
+ } else if (isIncrementalIndexing) {
+ var field = readHeaderFieldInternal(readInteger(6));
+ _table.addHeaderField(field);
+ headers.add(field);
+ } else if (isWithoutIndexing) {
+ headers.add(readHeaderFieldInternal(readInteger(4)));
+ } else if (isNeverIndexing) {
+ headers
+ .add(readHeaderFieldInternal(readInteger(4), neverIndexed: true));
+ } else if (isDynamicTableSizeUpdate) {
+ int newMaxSize = readInteger(5);
+ if (newMaxSize <= _maxHeaderTableSize) {
+ _table.updateMaxSize(newMaxSize);
+ } else {
+ throw new HPackDecodingException(
+ 'Dynamic table size update failed: '
+ 'A new value of $newMaxSize exceeds the limit of '
+ '$_maxHeaderTableSize');
+ }
+ } else {
+ throw new HPackDecodingException('Invalid encoding of headers.');
+ }
+ }
+ return headers;
+ } on RangeError catch (e) {
+ throw new HPackDecodingException('$e');
+ } on HuffmanDecodingException catch (e) {
+ throw new HPackDecodingException('$e');
+ }
+ }
+}
+
+/// A stateful HPACK encoder.
+// TODO: Currently we encode all headers:
+// - without huffman encoding
+// - without using the dynamic table
+class HPackEncoder {
+ int _maxHeaderTableSize = 4096;
+
+ final IndexTable _table = new IndexTable();
+
+ void updateMaxSendingHeaderTableSize(int newMaximumSize) {
+ // TODO: Once we start encoding via dynamic table we need to let the other
+ // side know the maximum table size we're using.
+ _maxHeaderTableSize = newMaximumSize;
+ }
+
+ List<int> encode(List<Header> headers) {
+ var bytesBuilder = new BytesBuilder();
+ int currentByte = 0;
+
+ void writeInteger(int prefixBits, int value) {
+ assert(prefixBits <= 8);
+
+ if (value < (1 << prefixBits) - 1) {
+ currentByte |= value;
+ bytesBuilder.addByte(currentByte);
+ } else {
+ // Length encodeded.
+ currentByte |= (1 << prefixBits) - 1;
+ value -= (1 << prefixBits) - 1;
+ bytesBuilder.addByte(currentByte);
+ bool done = false;
+ while (!done) {
+ currentByte = value & 0x7f;
+ value = value >> 7;
+ done = value == 0;
+ if (!done) currentByte |= 0x80;
+ bytesBuilder.addByte(currentByte);
+ }
+ }
+ currentByte = 0;
+ }
+
+ void writeStringLiteral(List<int> bytes) {
+ // TODO: Support huffman encoding.
+ currentByte = 0; // 1 would be huffman encoding
+ writeInteger(7, bytes.length);
+ bytesBuilder.add(bytes);
+ }
+
+ void writeLiteralHeaderWithoutIndexing(Header header) {
+ bytesBuilder.addByte(0);
+ writeStringLiteral(header.name);
+ writeStringLiteral(header.value);
+ }
+
+ for (var header in headers) {
+ writeLiteralHeaderWithoutIndexing(header);
+ }
+
+ return bytesBuilder.takeBytes();
+ }
+}
+
+class IndexTable {
+ static final List<Header> _staticTable = [
+ null,
+ new Header(ascii.encode(':authority'), const []),
+ new Header(ascii.encode(':method'), ascii.encode('GET')),
+ new Header(ascii.encode(':method'), ascii.encode('POST')),
+ new Header(ascii.encode(':path'), ascii.encode('/')),
+ new Header(ascii.encode(':path'), ascii.encode('/index.html')),
+ new Header(ascii.encode(':scheme'), ascii.encode('http')),
+ new Header(ascii.encode(':scheme'), ascii.encode('https')),
+ new Header(ascii.encode(':status'), ascii.encode('200')),
+ new Header(ascii.encode(':status'), ascii.encode('204')),
+ new Header(ascii.encode(':status'), ascii.encode('206')),
+ new Header(ascii.encode(':status'), ascii.encode('304')),
+ new Header(ascii.encode(':status'), ascii.encode('400')),
+ new Header(ascii.encode(':status'), ascii.encode('404')),
+ new Header(ascii.encode(':status'), ascii.encode('500')),
+ new Header(ascii.encode('accept-charset'), const []),
+ new Header(ascii.encode('accept-encoding'), ascii.encode('gzip, deflate')),
+ new Header(ascii.encode('accept-language'), const []),
+ new Header(ascii.encode('accept-ranges'), const []),
+ new Header(ascii.encode('accept'), const []),
+ new Header(ascii.encode('access-control-allow-origin'), const []),
+ new Header(ascii.encode('age'), const []),
+ new Header(ascii.encode('allow'), const []),
+ new Header(ascii.encode('authorization'), const []),
+ new Header(ascii.encode('cache-control'), const []),
+ new Header(ascii.encode('content-disposition'), const []),
+ new Header(ascii.encode('content-encoding'), const []),
+ new Header(ascii.encode('content-language'), const []),
+ new Header(ascii.encode('content-length'), const []),
+ new Header(ascii.encode('content-location'), const []),
+ new Header(ascii.encode('content-range'), const []),
+ new Header(ascii.encode('content-type'), const []),
+ new Header(ascii.encode('cookie'), const []),
+ new Header(ascii.encode('date'), const []),
+ new Header(ascii.encode('etag'), const []),
+ new Header(ascii.encode('expect'), const []),
+ new Header(ascii.encode('expires'), const []),
+ new Header(ascii.encode('from'), const []),
+ new Header(ascii.encode('host'), const []),
+ new Header(ascii.encode('if-match'), const []),
+ new Header(ascii.encode('if-modified-since'), const []),
+ new Header(ascii.encode('if-none-match'), const []),
+ new Header(ascii.encode('if-range'), const []),
+ new Header(ascii.encode('if-unmodified-since'), const []),
+ new Header(ascii.encode('last-modified'), const []),
+ new Header(ascii.encode('link'), const []),
+ new Header(ascii.encode('location'), const []),
+ new Header(ascii.encode('max-forwards'), const []),
+ new Header(ascii.encode('proxy-authenticate'), const []),
+ new Header(ascii.encode('proxy-authorization'), const []),
+ new Header(ascii.encode('range'), const []),
+ new Header(ascii.encode('referer'), const []),
+ new Header(ascii.encode('refresh'), const []),
+ new Header(ascii.encode('retry-after'), const []),
+ new Header(ascii.encode('server'), const []),
+ new Header(ascii.encode('set-cookie'), const []),
+ new Header(ascii.encode('strict-transport-security'), const []),
+ new Header(ascii.encode('transfer-encoding'), const []),
+ new Header(ascii.encode('user-agent'), const []),
+ new Header(ascii.encode('vary'), const []),
+ new Header(ascii.encode('via'), const []),
+ new Header(ascii.encode('www-authenticate'), const []),
+ ];
+
+ final List<Header> _dynamicTable = [];
+
+ /// The maximum size the dynamic table can grow to before entries need to be
+ /// evicted.
+ int _maximumSize = 4096;
+
+ /// The current size of the dynamic table.
+ int _currentSize = 0;
+
+ IndexTable();
+
+ /// Updates the maximum size which the dynamic table can grow to.
+ void updateMaxSize(int newMaxDynTableSize) {
+ _maximumSize = newMaxDynTableSize;
+ _reduce();
+ }
+
+ /// Lookup an item by index.
+ Header lookup(int index) {
+ if (index <= 0) {
+ throw new HPackDecodingException(
+ 'Invalid index (was: $index) for table lookup.');
+ }
+ if (index < _staticTable.length) {
+ return _staticTable[index];
+ }
+ index -= _staticTable.length;
+ if (index < _dynamicTable.length) {
+ return _dynamicTable[index];
+ }
+ throw new HPackDecodingException(
+ 'Invalid index (was: $index) for table lookup.');
+ }
+
+ /// Adds a new header field to the dynamic table - and evicts entries as
+ /// necessary.
+ void addHeaderField(Header header) {
+ _dynamicTable.insert(0, header);
+ _currentSize += _sizeOf(header);
+ _reduce();
+ }
+
+ /// Removes as many entries as required to be within the limit of
+ /// [_maximumSize].
+ void _reduce() {
+ while (_currentSize > _maximumSize) {
+ Header h = _dynamicTable.removeLast();
+ _currentSize -= _sizeOf(h);
+ }
+ }
+
+ /// Returns the "size" a [header] has.
+ ///
+ /// This is specified to be the number of octets of name/value plus 32.
+ int _sizeOf(Header header) => header.name.length + header.value.length + 32;
+}
diff --git a/http2/lib/src/hpack/huffman.dart b/http2/lib/src/hpack/huffman.dart
new file mode 100644
index 0000000..7dd4d09
--- /dev/null
+++ b/http2/lib/src/hpack/huffman.dart
@@ -0,0 +1,183 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library http2.src.hpack.huffman;
+
+import 'dart:io';
+
+import 'huffman_table.dart';
+
+class HuffmanDecodingException implements Exception {
+ final String _message;
+
+ HuffmanDecodingException(this._message);
+
+ String toString() => 'HuffmanDecodingException: $_message';
+}
+
+/// A codec used for encoding/decoding using a huffman codec.
+class HuffmanCodec {
+ final HuffmanEncoder _encoder;
+ final HuffmanDecoder _decoder;
+
+ HuffmanCodec(this._encoder, this._decoder);
+
+ List<int> decode(List<int> bytes) => _decoder.decode(bytes);
+
+ List<int> encode(List<int> bytes) => _encoder.encode(bytes);
+}
+
+/// A huffman decoder based on a [HuffmanTreeNode].
+class HuffmanDecoder {
+ final HuffmanTreeNode _root;
+
+ HuffmanDecoder(this._root);
+
+ /// Decodes [bytes] using a huffman tree.
+ List<int> decode(List<int> bytes) {
+ var buffer = new BytesBuilder();
+
+ int currentByteOffset = 0;
+ HuffmanTreeNode node = _root;
+ int currentDepth = 0;
+ while (currentByteOffset < bytes.length) {
+ var byte = bytes[currentByteOffset];
+ for (int currentBit = 7; currentBit >= 0; currentBit--) {
+ bool right = (byte >> currentBit) & 1 == 1;
+ if (right) {
+ node = node.right;
+ } else {
+ node = node.left;
+ }
+ currentDepth++;
+ if (node.value != null) {
+ if (node.value == EOS_BYTE) {
+ throw new HuffmanDecodingException(
+ 'More than 7 bit padding is not allowed. Found entire EOS '
+ 'encoding');
+ }
+ buffer.addByte(node.value);
+ node = _root;
+ currentDepth = 0;
+ }
+ }
+ currentByteOffset++;
+ }
+
+ if (node != _root) {
+ if (currentDepth > 7) {
+ throw new HuffmanDecodingException(
+ 'Incomplete encoding of a byte or more than 7 bit padding.');
+ }
+
+ while (node.right != null) node = node.right;
+
+ if (node.value != 256) {
+ throw new HuffmanDecodingException('Incomplete encoding of a byte.');
+ }
+ }
+
+ return buffer.takeBytes();
+ }
+}
+
+/// A huffman encoder based on a list of codewords.
+class HuffmanEncoder {
+ final List<EncodedHuffmanValue> _codewords;
+
+ HuffmanEncoder(this._codewords);
+
+ /// Encodes [bytes] using a list of codewords.
+ List<int> encode(List<int> bytes) {
+ var buffer = new BytesBuilder();
+
+ int currentByte = 0;
+ int currentBitOffset = 7;
+
+ writeValue(int value, int numBits) {
+ int i = numBits - 1;
+ while (i >= 0) {
+ if (currentBitOffset == 7 && i >= 7) {
+ assert(currentByte == 0);
+
+ buffer.addByte((value >> (i - 7)) & 0xff);
+ currentBitOffset = 7;
+ currentByte = 0;
+ i -= 8;
+ } else {
+ currentByte |= ((value >> i) & 1) << currentBitOffset;
+
+ currentBitOffset--;
+ if (currentBitOffset == -1) {
+ buffer.addByte(currentByte);
+ currentBitOffset = 7;
+ currentByte = 0;
+ }
+ i--;
+ }
+ }
+ }
+
+ for (int i = 0; i < bytes.length; i++) {
+ var byte = bytes[i];
+ var value = _codewords[byte];
+ writeValue(value.encodedBytes, value.numBits);
+ }
+
+ if (currentBitOffset < 7) {
+ writeValue(0xff, 1 + currentBitOffset);
+ }
+
+ return buffer.takeBytes();
+ }
+}
+
+/// Specifies the encoding of a specific value using huffman encoding.
+class EncodedHuffmanValue {
+ /// An integer representation of the encoded bit-string.
+ final int encodedBytes;
+
+ /// The number of bits in [encodedBytes].
+ final int numBits;
+
+ const EncodedHuffmanValue(this.encodedBytes, this.numBits);
+}
+
+/// A node in the huffman tree.
+class HuffmanTreeNode {
+ HuffmanTreeNode left;
+ HuffmanTreeNode right;
+ int value;
+}
+
+/// Generates a huffman decoding tree.
+HuffmanTreeNode generateHuffmanTree(List<EncodedHuffmanValue> valueEncodings) {
+ HuffmanTreeNode root = new HuffmanTreeNode();
+
+ for (int byteOffset = 0; byteOffset < valueEncodings.length; byteOffset++) {
+ var entry = valueEncodings[byteOffset];
+
+ HuffmanTreeNode current = root;
+ for (int bitNr = 0; bitNr < entry.numBits; bitNr++) {
+ bool right =
+ ((entry.encodedBytes >> (entry.numBits - bitNr - 1)) & 1) == 1;
+
+ if (right) {
+ if (current.right == null) {
+ current.right = new HuffmanTreeNode();
+ }
+ current = current.right;
+ } else {
+ if (current.left == null) {
+ current.left = new HuffmanTreeNode();
+ }
+ current = current.left;
+ }
+ }
+
+ current.value = byteOffset;
+ }
+
+ return root;
+}
diff --git a/http2/lib/src/hpack/huffman_table.dart b/http2/lib/src/hpack/huffman_table.dart
new file mode 100644
index 0000000..90471c8
--- /dev/null
+++ b/http2/lib/src/hpack/huffman_table.dart
@@ -0,0 +1,278 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library http2.hpack.huffman_table;
+
+import 'huffman.dart';
+
+/// The huffman codec for encoding/decoding HTTP/2 header blocks.
+final HuffmanCodec http2HuffmanCodec = new HuffmanCodec(
+ new HuffmanEncoder(_codeWords),
+ new HuffmanDecoder(generateHuffmanTree(_codeWords)));
+
+/// This is the integer representing the End-of-String symbol
+/// (it is not representable by a byte).
+const int EOS_BYTE = 256;
+
+/// This list of byte encodings via huffman encoding was generated from the
+/// HPACK specification.
+const List<EncodedHuffmanValue> _codeWords = const <EncodedHuffmanValue>[
+ const EncodedHuffmanValue(0x1ff8, 13),
+ const EncodedHuffmanValue(0x7fffd8, 23),
+ const EncodedHuffmanValue(0xfffffe2, 28),
+ const EncodedHuffmanValue(0xfffffe3, 28),
+ const EncodedHuffmanValue(0xfffffe4, 28),
+ const EncodedHuffmanValue(0xfffffe5, 28),
+ const EncodedHuffmanValue(0xfffffe6, 28),
+ const EncodedHuffmanValue(0xfffffe7, 28),
+ const EncodedHuffmanValue(0xfffffe8, 28),
+ const EncodedHuffmanValue(0xffffea, 24),
+ const EncodedHuffmanValue(0x3ffffffc, 30),
+ const EncodedHuffmanValue(0xfffffe9, 28),
+ const EncodedHuffmanValue(0xfffffea, 28),
+ const EncodedHuffmanValue(0x3ffffffd, 30),
+ const EncodedHuffmanValue(0xfffffeb, 28),
+ const EncodedHuffmanValue(0xfffffec, 28),
+ const EncodedHuffmanValue(0xfffffed, 28),
+ const EncodedHuffmanValue(0xfffffee, 28),
+ const EncodedHuffmanValue(0xfffffef, 28),
+ const EncodedHuffmanValue(0xffffff0, 28),
+ const EncodedHuffmanValue(0xffffff1, 28),
+ const EncodedHuffmanValue(0xffffff2, 28),
+ const EncodedHuffmanValue(0x3ffffffe, 30),
+ const EncodedHuffmanValue(0xffffff3, 28),
+ const EncodedHuffmanValue(0xffffff4, 28),
+ const EncodedHuffmanValue(0xffffff5, 28),
+ const EncodedHuffmanValue(0xffffff6, 28),
+ const EncodedHuffmanValue(0xffffff7, 28),
+ const EncodedHuffmanValue(0xffffff8, 28),
+ const EncodedHuffmanValue(0xffffff9, 28),
+ const EncodedHuffmanValue(0xffffffa, 28),
+ const EncodedHuffmanValue(0xffffffb, 28),
+ const EncodedHuffmanValue(0x14, 6),
+ const EncodedHuffmanValue(0x3f8, 10),
+ const EncodedHuffmanValue(0x3f9, 10),
+ const EncodedHuffmanValue(0xffa, 12),
+ const EncodedHuffmanValue(0x1ff9, 13),
+ const EncodedHuffmanValue(0x15, 6),
+ const EncodedHuffmanValue(0xf8, 8),
+ const EncodedHuffmanValue(0x7fa, 11),
+ const EncodedHuffmanValue(0x3fa, 10),
+ const EncodedHuffmanValue(0x3fb, 10),
+ const EncodedHuffmanValue(0xf9, 8),
+ const EncodedHuffmanValue(0x7fb, 11),
+ const EncodedHuffmanValue(0xfa, 8),
+ const EncodedHuffmanValue(0x16, 6),
+ const EncodedHuffmanValue(0x17, 6),
+ const EncodedHuffmanValue(0x18, 6),
+ const EncodedHuffmanValue(0x0, 5),
+ const EncodedHuffmanValue(0x1, 5),
+ const EncodedHuffmanValue(0x2, 5),
+ const EncodedHuffmanValue(0x19, 6),
+ const EncodedHuffmanValue(0x1a, 6),
+ const EncodedHuffmanValue(0x1b, 6),
+ const EncodedHuffmanValue(0x1c, 6),
+ const EncodedHuffmanValue(0x1d, 6),
+ const EncodedHuffmanValue(0x1e, 6),
+ const EncodedHuffmanValue(0x1f, 6),
+ const EncodedHuffmanValue(0x5c, 7),
+ const EncodedHuffmanValue(0xfb, 8),
+ const EncodedHuffmanValue(0x7ffc, 15),
+ const EncodedHuffmanValue(0x20, 6),
+ const EncodedHuffmanValue(0xffb, 12),
+ const EncodedHuffmanValue(0x3fc, 10),
+ const EncodedHuffmanValue(0x1ffa, 13),
+ const EncodedHuffmanValue(0x21, 6),
+ const EncodedHuffmanValue(0x5d, 7),
+ const EncodedHuffmanValue(0x5e, 7),
+ const EncodedHuffmanValue(0x5f, 7),
+ const EncodedHuffmanValue(0x60, 7),
+ const EncodedHuffmanValue(0x61, 7),
+ const EncodedHuffmanValue(0x62, 7),
+ const EncodedHuffmanValue(0x63, 7),
+ const EncodedHuffmanValue(0x64, 7),
+ const EncodedHuffmanValue(0x65, 7),
+ const EncodedHuffmanValue(0x66, 7),
+ const EncodedHuffmanValue(0x67, 7),
+ const EncodedHuffmanValue(0x68, 7),
+ const EncodedHuffmanValue(0x69, 7),
+ const EncodedHuffmanValue(0x6a, 7),
+ const EncodedHuffmanValue(0x6b, 7),
+ const EncodedHuffmanValue(0x6c, 7),
+ const EncodedHuffmanValue(0x6d, 7),
+ const EncodedHuffmanValue(0x6e, 7),
+ const EncodedHuffmanValue(0x6f, 7),
+ const EncodedHuffmanValue(0x70, 7),
+ const EncodedHuffmanValue(0x71, 7),
+ const EncodedHuffmanValue(0x72, 7),
+ const EncodedHuffmanValue(0xfc, 8),
+ const EncodedHuffmanValue(0x73, 7),
+ const EncodedHuffmanValue(0xfd, 8),
+ const EncodedHuffmanValue(0x1ffb, 13),
+ const EncodedHuffmanValue(0x7fff0, 19),
+ const EncodedHuffmanValue(0x1ffc, 13),
+ const EncodedHuffmanValue(0x3ffc, 14),
+ const EncodedHuffmanValue(0x22, 6),
+ const EncodedHuffmanValue(0x7ffd, 15),
+ const EncodedHuffmanValue(0x3, 5),
+ const EncodedHuffmanValue(0x23, 6),
+ const EncodedHuffmanValue(0x4, 5),
+ const EncodedHuffmanValue(0x24, 6),
+ const EncodedHuffmanValue(0x5, 5),
+ const EncodedHuffmanValue(0x25, 6),
+ const EncodedHuffmanValue(0x26, 6),
+ const EncodedHuffmanValue(0x27, 6),
+ const EncodedHuffmanValue(0x6, 5),
+ const EncodedHuffmanValue(0x74, 7),
+ const EncodedHuffmanValue(0x75, 7),
+ const EncodedHuffmanValue(0x28, 6),
+ const EncodedHuffmanValue(0x29, 6),
+ const EncodedHuffmanValue(0x2a, 6),
+ const EncodedHuffmanValue(0x7, 5),
+ const EncodedHuffmanValue(0x2b, 6),
+ const EncodedHuffmanValue(0x76, 7),
+ const EncodedHuffmanValue(0x2c, 6),
+ const EncodedHuffmanValue(0x8, 5),
+ const EncodedHuffmanValue(0x9, 5),
+ const EncodedHuffmanValue(0x2d, 6),
+ const EncodedHuffmanValue(0x77, 7),
+ const EncodedHuffmanValue(0x78, 7),
+ const EncodedHuffmanValue(0x79, 7),
+ const EncodedHuffmanValue(0x7a, 7),
+ const EncodedHuffmanValue(0x7b, 7),
+ const EncodedHuffmanValue(0x7ffe, 15),
+ const EncodedHuffmanValue(0x7fc, 11),
+ const EncodedHuffmanValue(0x3ffd, 14),
+ const EncodedHuffmanValue(0x1ffd, 13),
+ const EncodedHuffmanValue(0xffffffc, 28),
+ const EncodedHuffmanValue(0xfffe6, 20),
+ const EncodedHuffmanValue(0x3fffd2, 22),
+ const EncodedHuffmanValue(0xfffe7, 20),
+ const EncodedHuffmanValue(0xfffe8, 20),
+ const EncodedHuffmanValue(0x3fffd3, 22),
+ const EncodedHuffmanValue(0x3fffd4, 22),
+ const EncodedHuffmanValue(0x3fffd5, 22),
+ const EncodedHuffmanValue(0x7fffd9, 23),
+ const EncodedHuffmanValue(0x3fffd6, 22),
+ const EncodedHuffmanValue(0x7fffda, 23),
+ const EncodedHuffmanValue(0x7fffdb, 23),
+ const EncodedHuffmanValue(0x7fffdc, 23),
+ const EncodedHuffmanValue(0x7fffdd, 23),
+ const EncodedHuffmanValue(0x7fffde, 23),
+ const EncodedHuffmanValue(0xffffeb, 24),
+ const EncodedHuffmanValue(0x7fffdf, 23),
+ const EncodedHuffmanValue(0xffffec, 24),
+ const EncodedHuffmanValue(0xffffed, 24),
+ const EncodedHuffmanValue(0x3fffd7, 22),
+ const EncodedHuffmanValue(0x7fffe0, 23),
+ const EncodedHuffmanValue(0xffffee, 24),
+ const EncodedHuffmanValue(0x7fffe1, 23),
+ const EncodedHuffmanValue(0x7fffe2, 23),
+ const EncodedHuffmanValue(0x7fffe3, 23),
+ const EncodedHuffmanValue(0x7fffe4, 23),
+ const EncodedHuffmanValue(0x1fffdc, 21),
+ const EncodedHuffmanValue(0x3fffd8, 22),
+ const EncodedHuffmanValue(0x7fffe5, 23),
+ const EncodedHuffmanValue(0x3fffd9, 22),
+ const EncodedHuffmanValue(0x7fffe6, 23),
+ const EncodedHuffmanValue(0x7fffe7, 23),
+ const EncodedHuffmanValue(0xffffef, 24),
+ const EncodedHuffmanValue(0x3fffda, 22),
+ const EncodedHuffmanValue(0x1fffdd, 21),
+ const EncodedHuffmanValue(0xfffe9, 20),
+ const EncodedHuffmanValue(0x3fffdb, 22),
+ const EncodedHuffmanValue(0x3fffdc, 22),
+ const EncodedHuffmanValue(0x7fffe8, 23),
+ const EncodedHuffmanValue(0x7fffe9, 23),
+ const EncodedHuffmanValue(0x1fffde, 21),
+ const EncodedHuffmanValue(0x7fffea, 23),
+ const EncodedHuffmanValue(0x3fffdd, 22),
+ const EncodedHuffmanValue(0x3fffde, 22),
+ const EncodedHuffmanValue(0xfffff0, 24),
+ const EncodedHuffmanValue(0x1fffdf, 21),
+ const EncodedHuffmanValue(0x3fffdf, 22),
+ const EncodedHuffmanValue(0x7fffeb, 23),
+ const EncodedHuffmanValue(0x7fffec, 23),
+ const EncodedHuffmanValue(0x1fffe0, 21),
+ const EncodedHuffmanValue(0x1fffe1, 21),
+ const EncodedHuffmanValue(0x3fffe0, 22),
+ const EncodedHuffmanValue(0x1fffe2, 21),
+ const EncodedHuffmanValue(0x7fffed, 23),
+ const EncodedHuffmanValue(0x3fffe1, 22),
+ const EncodedHuffmanValue(0x7fffee, 23),
+ const EncodedHuffmanValue(0x7fffef, 23),
+ const EncodedHuffmanValue(0xfffea, 20),
+ const EncodedHuffmanValue(0x3fffe2, 22),
+ const EncodedHuffmanValue(0x3fffe3, 22),
+ const EncodedHuffmanValue(0x3fffe4, 22),
+ const EncodedHuffmanValue(0x7ffff0, 23),
+ const EncodedHuffmanValue(0x3fffe5, 22),
+ const EncodedHuffmanValue(0x3fffe6, 22),
+ const EncodedHuffmanValue(0x7ffff1, 23),
+ const EncodedHuffmanValue(0x3ffffe0, 26),
+ const EncodedHuffmanValue(0x3ffffe1, 26),
+ const EncodedHuffmanValue(0xfffeb, 20),
+ const EncodedHuffmanValue(0x7fff1, 19),
+ const EncodedHuffmanValue(0x3fffe7, 22),
+ const EncodedHuffmanValue(0x7ffff2, 23),
+ const EncodedHuffmanValue(0x3fffe8, 22),
+ const EncodedHuffmanValue(0x1ffffec, 25),
+ const EncodedHuffmanValue(0x3ffffe2, 26),
+ const EncodedHuffmanValue(0x3ffffe3, 26),
+ const EncodedHuffmanValue(0x3ffffe4, 26),
+ const EncodedHuffmanValue(0x7ffffde, 27),
+ const EncodedHuffmanValue(0x7ffffdf, 27),
+ const EncodedHuffmanValue(0x3ffffe5, 26),
+ const EncodedHuffmanValue(0xfffff1, 24),
+ const EncodedHuffmanValue(0x1ffffed, 25),
+ const EncodedHuffmanValue(0x7fff2, 19),
+ const EncodedHuffmanValue(0x1fffe3, 21),
+ const EncodedHuffmanValue(0x3ffffe6, 26),
+ const EncodedHuffmanValue(0x7ffffe0, 27),
+ const EncodedHuffmanValue(0x7ffffe1, 27),
+ const EncodedHuffmanValue(0x3ffffe7, 26),
+ const EncodedHuffmanValue(0x7ffffe2, 27),
+ const EncodedHuffmanValue(0xfffff2, 24),
+ const EncodedHuffmanValue(0x1fffe4, 21),
+ const EncodedHuffmanValue(0x1fffe5, 21),
+ const EncodedHuffmanValue(0x3ffffe8, 26),
+ const EncodedHuffmanValue(0x3ffffe9, 26),
+ const EncodedHuffmanValue(0xffffffd, 28),
+ const EncodedHuffmanValue(0x7ffffe3, 27),
+ const EncodedHuffmanValue(0x7ffffe4, 27),
+ const EncodedHuffmanValue(0x7ffffe5, 27),
+ const EncodedHuffmanValue(0xfffec, 20),
+ const EncodedHuffmanValue(0xfffff3, 24),
+ const EncodedHuffmanValue(0xfffed, 20),
+ const EncodedHuffmanValue(0x1fffe6, 21),
+ const EncodedHuffmanValue(0x3fffe9, 22),
+ const EncodedHuffmanValue(0x1fffe7, 21),
+ const EncodedHuffmanValue(0x1fffe8, 21),
+ const EncodedHuffmanValue(0x7ffff3, 23),
+ const EncodedHuffmanValue(0x3fffea, 22),
+ const EncodedHuffmanValue(0x3fffeb, 22),
+ const EncodedHuffmanValue(0x1ffffee, 25),
+ const EncodedHuffmanValue(0x1ffffef, 25),
+ const EncodedHuffmanValue(0xfffff4, 24),
+ const EncodedHuffmanValue(0xfffff5, 24),
+ const EncodedHuffmanValue(0x3ffffea, 26),
+ const EncodedHuffmanValue(0x7ffff4, 23),
+ const EncodedHuffmanValue(0x3ffffeb, 26),
+ const EncodedHuffmanValue(0x7ffffe6, 27),
+ const EncodedHuffmanValue(0x3ffffec, 26),
+ const EncodedHuffmanValue(0x3ffffed, 26),
+ const EncodedHuffmanValue(0x7ffffe7, 27),
+ const EncodedHuffmanValue(0x7ffffe8, 27),
+ const EncodedHuffmanValue(0x7ffffe9, 27),
+ const EncodedHuffmanValue(0x7ffffea, 27),
+ const EncodedHuffmanValue(0x7ffffeb, 27),
+ const EncodedHuffmanValue(0xffffffe, 28),
+ const EncodedHuffmanValue(0x7ffffec, 27),
+ const EncodedHuffmanValue(0x7ffffed, 27),
+ const EncodedHuffmanValue(0x7ffffee, 27),
+ const EncodedHuffmanValue(0x7ffffef, 27),
+ const EncodedHuffmanValue(0x7fffff0, 27),
+ const EncodedHuffmanValue(0x3ffffee, 26),
+ const EncodedHuffmanValue(0x3fffffff, 30),
+];
diff --git a/http2/lib/src/ping/ping_handler.dart b/http2/lib/src/ping/ping_handler.dart
new file mode 100644
index 0000000..bd8bc17
--- /dev/null
+++ b/http2/lib/src/ping/ping_handler.dart
@@ -0,0 +1,62 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library http2.src.ping.ping_handler;
+
+import 'dart:async';
+
+import '../error_handler.dart';
+import '../frames/frames.dart';
+import '../sync_errors.dart';
+
+/// Responsible for pinging the other end and for handling pings from the
+/// other end.
+// TODO: We currently write unconditionally to the [FrameWriter]: we might want
+// to consider be more aware what [Framewriter.bufferIndicator.wouldBuffer]
+// says.
+class PingHandler extends Object with TerminatableMixin {
+ final FrameWriter _frameWriter;
+ final Map<int, Completer> _remainingPings = {};
+ int _nextId = 1;
+
+ PingHandler(this._frameWriter);
+
+ void onTerminated(error) {
+ var values = _remainingPings.values.toList();
+ _remainingPings.clear();
+ values.forEach((Completer c) => c.completeError(error));
+ }
+
+ void processPingFrame(PingFrame frame) {
+ ensureNotTerminatedSync(() {
+ if (frame.header.streamId != 0) {
+ throw new ProtocolException('Ping frames must have a stream id of 0.');
+ }
+
+ if (!frame.hasAckFlag) {
+ _frameWriter.writePingFrame(frame.opaqueData, ack: true);
+ } else {
+ Completer c = _remainingPings.remove(frame.opaqueData);
+ if (c != null) {
+ c.complete();
+ } else {
+ // NOTE: It is not specified what happens when one gets an ACK for a
+ // ping we never sent. We be very strict and fail in this case.
+ throw new ProtocolException(
+ 'Received ping ack with unknown opaque data.');
+ }
+ }
+ });
+ }
+
+ Future ping() {
+ return ensureNotTerminatedAsync(() {
+ Completer c = new Completer();
+ var id = _nextId++;
+ _remainingPings[id] = c;
+ _frameWriter.writePingFrame(id);
+ return c.future;
+ });
+ }
+}
diff --git a/http2/lib/src/settings/settings.dart b/http2/lib/src/settings/settings.dart
new file mode 100644
index 0000000..6ee2284
--- /dev/null
+++ b/http2/lib/src/settings/settings.dart
@@ -0,0 +1,225 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library http2.src.settings;
+
+import 'dart:async';
+
+import '../error_handler.dart';
+import '../frames/frames.dart';
+import '../hpack/hpack.dart';
+import '../sync_errors.dart';
+
+/// The settings a remote peer can choose to set.
+class ActiveSettings {
+ /// Allows the sender to inform the remote endpoint of the maximum size of the
+ /// header compression table used to decode header blocks, in octets. The
+ /// encoder can select any size equal to or less than this value by using
+ /// signaling specific to the header compression format inside a header block.
+ /// The initial value is 4,096 octets.
+ int headerTableSize;
+
+ /// This setting can be use to disable server push (Section 8.2). An endpoint
+ /// MUST NOT send a PUSH_PROMISE frame if it receives this parameter set to a
+ /// value of 0. An endpoint that has both set this parameter to 0 and had it
+ /// acknowledged MUST treat the receipt of a PUSH_PROMISE frame as a
+ /// connection error (Section 5.4.1) of type PROTOCOL_ERROR.
+ ///
+ /// The initial value is 1, which indicates that server push is permitted.
+ /// Any value other than 0 or 1 MUST be treated as a connection error
+ /// (Section 5.4.1) of type PROTOCOL_ERROR.
+ bool enablePush;
+
+ /// Indicates the maximum number of concurrent streams that the sender will
+ /// allow. This limit is directional: it applies to the number of streams that
+ /// the sender permits the receiver to create. Initially there is no limit to
+ /// this value. It is recommended that this value be no smaller than 100, so
+ /// as to not unnecessarily limit parallelism.
+ ///
+ /// A value of 0 for SETTINGS_MAX_CONCURRENT_STREAMS SHOULD NOT be treated as
+ /// special by endpoints. A zero value does prevent the creation of new
+ /// streams, however this can also happen for any limit that is exhausted with
+ /// active streams. Servers SHOULD only set a zero value for short durations;
+ /// if a server does not wish to accept requests, closing the connection is
+ /// more appropriate.
+ int maxConcurrentStreams;
+
+ /// Indicates the sender's initial window size (in octets) for stream level
+ /// flow control. The initial value is 2^16-1 (65,535) octets.
+ ///
+ /// This setting affects the window size of all streams, including existing
+ /// streams, see Section 6.9.2.
+ /// Values above the maximum flow control window size of 231-1 MUST be treated
+ /// as a connection error (Section 5.4.1) of type FLOW_CONTROL_ERROR.
+ int initialWindowSize;
+
+ /// Indicates the size of the largest frame payload that the sender is willing
+ /// to receive, in octets.
+ ///
+ /// The initial value is 2^14 (16,384) octets. The value advertised by an
+ /// endpoint MUST be between this initial value and the maximum allowed frame
+ /// size (2^24-1 or 16,777,215 octets), inclusive. Values outside this range
+ /// MUST be treated as a connection error (Section 5.4.1) of type
+ /// PROTOCOL_ERROR.
+ int maxFrameSize;
+
+ /// This advisory setting informs a peer of the maximum size of header list
+ /// that the sender is prepared to accept, in octets. The value is based on
+ /// the uncompressed size of header fields, including the length of the name
+ /// and value in octets plus an overhead of 32 octets for each header field.
+ ///
+ /// For any given request, a lower limit than what is advertised MAY be
+ /// enforced. The initial value of this setting is unlimited.
+ int maxHeaderListSize;
+
+ ActiveSettings(
+ {this.headerTableSize: 4096,
+ this.enablePush: true,
+ this.maxConcurrentStreams,
+ this.initialWindowSize: (1 << 16) - 1,
+ this.maxFrameSize: (1 << 14),
+ this.maxHeaderListSize});
+}
+
+/// Handles remote and local connection [Setting]s.
+///
+/// Incoming [SettingsFrame]s will be handled here to update the peer settings.
+/// Changes to [_toBeAcknowledgedSettings] can be made, the peer will then be
+/// notified of the setting changes it should use.
+class SettingsHandler extends Object with TerminatableMixin {
+ /// Certain settings changes can change the maximum allowed dynamic table
+ /// size used by the HPack encoder.
+ final HPackEncoder _hpackEncoder;
+
+ final FrameWriter _frameWriter;
+
+ /// A list of outstanding setting changes.
+ final List<List<Setting>> _toBeAcknowledgedSettings = [];
+
+ /// A list of completers for outstanding setting changes.
+ final List<Completer> _toBeAcknowledgedCompleters = [];
+
+ /// The local settings, which the remote side ACKed to obey.
+ final ActiveSettings _acknowledgedSettings;
+
+ /// The peer settings, which we ACKed and are obeying.
+ final ActiveSettings _peerSettings;
+
+ final _onInitialWindowSizeChangeController =
+ new StreamController<int>.broadcast(sync: true);
+
+ /// Events are fired when a SettingsFrame changes the initial size
+ /// of stream windows.
+ Stream<int> get onInitialWindowSizeChange =>
+ _onInitialWindowSizeChangeController.stream;
+
+ SettingsHandler(this._hpackEncoder, this._frameWriter,
+ this._acknowledgedSettings, this._peerSettings);
+
+ /// The settings for this endpoint of the connection which the remote peer
+ /// has ACKed and uses.
+ ActiveSettings get acknowledgedSettings => _acknowledgedSettings;
+
+ /// The settings for the remote endpoint of the connection which this
+ /// endpoint should use.
+ ActiveSettings get peerSettings => _peerSettings;
+
+ /// Handles an incoming [SettingsFrame] which can be an ACK or a settings
+ /// change.
+ void handleSettingsFrame(SettingsFrame frame) {
+ ensureNotTerminatedSync(() {
+ assert(frame.header.streamId == 0);
+
+ if (frame.hasAckFlag) {
+ assert(frame.header.length == 0);
+
+ if (_toBeAcknowledgedSettings.isEmpty) {
+ // NOTE: The specification does not say anything about ACKed settings
+ // which were never sent to the other side. We consider this definitly
+ // an error.
+ throw new ProtocolException(
+ 'Received an acknowledged settings frame which did not have a '
+ 'outstanding settings request.');
+ }
+ List<Setting> settingChanges = _toBeAcknowledgedSettings.removeAt(0);
+ Completer completer = _toBeAcknowledgedCompleters.removeAt(0);
+ _modifySettings(_acknowledgedSettings, settingChanges, false);
+ completer.complete();
+ } else {
+ _modifySettings(_peerSettings, frame.settings, true);
+ _frameWriter.writeSettingsAckFrame();
+ }
+ });
+ }
+
+ void onTerminated(error) {
+ _toBeAcknowledgedSettings.clear();
+ _toBeAcknowledgedCompleters
+ .forEach((Completer c) => c.completeError(error));
+ }
+
+ Future changeSettings(List<Setting> changes) {
+ return ensureNotTerminatedAsync(() {
+ // TODO: Have a timeout: When ACK doesn't get back in a reasonable time
+ // frame we should quit with ErrorCode.SETTINGS_TIMEOUT.
+ var completer = new Completer();
+ _toBeAcknowledgedSettings.add(changes);
+ _toBeAcknowledgedCompleters.add(completer);
+ _frameWriter.writeSettingsFrame(changes);
+ return completer.future;
+ });
+ }
+
+ void _modifySettings(
+ ActiveSettings base, List<Setting> changes, bool peerSettings) {
+ for (var setting in changes) {
+ switch (setting.identifier) {
+ case Setting.SETTINGS_ENABLE_PUSH:
+ if (setting.value == 0) {
+ base.enablePush = false;
+ } else if (setting.value == 1) {
+ base.enablePush = true;
+ } else {
+ throw new ProtocolException(
+ 'The push setting can be only set to 0 or 1.');
+ }
+ break;
+
+ case Setting.SETTINGS_HEADER_TABLE_SIZE:
+ base.headerTableSize = setting.value;
+ if (peerSettings) {
+ _hpackEncoder.updateMaxSendingHeaderTableSize(base.headerTableSize);
+ }
+ break;
+
+ case Setting.SETTINGS_MAX_HEADER_LIST_SIZE:
+ // TODO: Propagate this signal to the HPackContext.
+ base.maxHeaderListSize = setting.value;
+ break;
+
+ case Setting.SETTINGS_MAX_CONCURRENT_STREAMS:
+ // NOTE: We will not force closing of existing streams if the limit is
+ // lower than the current number of open streams. But we will prevent
+ // new streams from being created if the number of existing streams
+ // is above this limit.
+ base.maxConcurrentStreams = setting.value;
+ break;
+
+ case Setting.SETTINGS_INITIAL_WINDOW_SIZE:
+ if (setting.value < (1 << 31)) {
+ int difference = setting.value - base.initialWindowSize;
+ _onInitialWindowSizeChangeController.add(difference);
+ base.initialWindowSize = setting.value;
+ } else {
+ throw new FlowControlException('Invalid initial window size.');
+ }
+ break;
+
+ default:
+ // Spec says to ignore unknown settings.
+ break;
+ }
+ }
+ }
+}
diff --git a/http2/lib/src/streams/stream_handler.dart b/http2/lib/src/streams/stream_handler.dart
new file mode 100644
index 0000000..0f79b52
--- /dev/null
+++ b/http2/lib/src/streams/stream_handler.dart
@@ -0,0 +1,874 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library http2.src.stream_handler;
+
+import 'dart:async';
+import 'dart:math';
+
+import '../../transport.dart';
+
+import '../connection.dart';
+import '../error_handler.dart';
+import '../flowcontrol/connection_queues.dart';
+import '../flowcontrol/queue_messages.dart';
+import '../flowcontrol/stream_queues.dart';
+import '../flowcontrol/window.dart';
+import '../flowcontrol/window_handler.dart';
+import '../frames/frames.dart';
+import '../hpack/hpack.dart';
+import '../settings/settings.dart';
+import '../sync_errors.dart';
+
+/// Represents the current state of a stream.
+enum StreamState {
+ ReservedLocal,
+ ReservedRemote,
+ Idle,
+ Open,
+ HalfClosedLocal,
+ HalfClosedRemote,
+ Closed,
+
+ /// The [Terminated] state is an artificial state and signals that this stream
+ /// has been forcefully terminated.
+ Terminated,
+}
+
+/// Represents a HTTP/2 stream.
+class Http2StreamImpl extends TransportStream
+ implements ClientTransportStream, ServerTransportStream {
+ /// The id of this stream.
+ ///
+ /// * odd numbered streams are client streams
+ /// * even numbered streams are opened from the server
+ final int id;
+
+ // The queue for incoming [StreamMessage]s.
+ final StreamMessageQueueIn incomingQueue;
+
+ // The queue for outgoing [StreamMessage]s.
+ final StreamMessageQueueOut outgoingQueue;
+
+ // The stream controller to which the application can
+ // add outgoing messages.
+ final StreamController<StreamMessage> _outgoingC;
+
+ final OutgoingStreamWindowHandler windowHandler;
+
+ // The state of this stream.
+ StreamState state = StreamState.Idle;
+
+ // Error code from RST_STREAM frame, if the stream has been terminated
+ // remotely.
+ int _terminatedErrorCode;
+
+ // Termination handler. Invoked if the stream receives an RST_STREAM frame.
+ void Function(int) _onTerminated;
+
+ final ZoneUnaryCallback<bool, Http2StreamImpl> _canPushFun;
+ final ZoneBinaryCallback<ServerTransportStream, Http2StreamImpl, List<Header>>
+ _pushStreamFun;
+ final ZoneUnaryCallback<dynamic, Http2StreamImpl> _terminateStreamFun;
+
+ StreamSubscription _outgoingCSubscription;
+
+ Http2StreamImpl(
+ this.incomingQueue,
+ this.outgoingQueue,
+ this._outgoingC,
+ this.id,
+ this.windowHandler,
+ this._canPushFun,
+ this._pushStreamFun,
+ this._terminateStreamFun);
+
+ /// A stream of data and/or headers from the remote end.
+ Stream<StreamMessage> get incomingMessages => incomingQueue.messages;
+
+ /// A sink for writing data and/or headers to the remote end.
+ StreamSink<StreamMessage> get outgoingMessages => _outgoingC.sink;
+
+ /// Streams which the server pushed to this endpoint.
+ Stream<TransportStreamPush> get peerPushes => incomingQueue.serverPushes;
+
+ bool get canPush => _canPushFun(this);
+
+ /// Pushes a new stream to a client.
+ ///
+ /// The [requestHeaders] are the headers to which the pushed stream
+ /// responds to.
+ ServerTransportStream push(List<Header> requestHeaders) =>
+ _pushStreamFun(this, requestHeaders);
+
+ void terminate() => _terminateStreamFun(this);
+
+ set onTerminated(void handler(int v)) {
+ _onTerminated = handler;
+ if (_terminatedErrorCode != null && _onTerminated != null) {
+ _onTerminated(_terminatedErrorCode);
+ }
+ }
+
+ void _handleTerminated(int errorCode) {
+ _terminatedErrorCode = errorCode;
+ if (_onTerminated != null) {
+ _onTerminated(_terminatedErrorCode);
+ }
+ }
+}
+
+/// Handles [Frame]s with a non-zero stream-id.
+///
+/// It keeps track of open streams, their state, their queues, forwards
+/// messages from the connection level to stream level and vise versa.
+// TODO: Handle stream/connection queue errors & forward to connection object.
+class StreamHandler extends Object with TerminatableMixin, ClosableMixin {
+ static const int MAX_STREAM_ID = (1 << 31) - 1;
+
+ final FrameWriter _frameWriter;
+ final ConnectionMessageQueueIn incomingQueue;
+ final ConnectionMessageQueueOut outgoingQueue;
+
+ final StreamController<TransportStream> _newStreamsC = new StreamController();
+
+ final ActiveSettings _peerSettings;
+ final ActiveSettings _localSettings;
+
+ final Map<int, Http2StreamImpl> _openStreams = {};
+ int nextStreamId;
+ int lastRemoteStreamId;
+
+ int _highestStreamIdReceived = 0;
+
+ /// Represents the highest stream id this connection has received from the
+ /// remote side.
+ int get highestPeerInitiatedStream => _highestStreamIdReceived;
+
+ bool get isServer => nextStreamId.isEven;
+
+ bool get ranOutOfStreamIds => _ranOutOfStreamIds();
+
+ /// Whether it is possible to open a new stream to the remote end (e.g. based
+ /// on whether we have reached the limit of maximum concurrent open streams).
+ bool get canOpenStream => _canCreateNewStream();
+
+ final ActiveStateHandler _onActiveStateChanged;
+
+ StreamHandler._(
+ this._frameWriter,
+ this.incomingQueue,
+ this.outgoingQueue,
+ this._peerSettings,
+ this._localSettings,
+ this._onActiveStateChanged,
+ this.nextStreamId,
+ this.lastRemoteStreamId);
+
+ factory StreamHandler.client(
+ FrameWriter writer,
+ ConnectionMessageQueueIn incomingQueue,
+ ConnectionMessageQueueOut outgoingQueue,
+ ActiveSettings peerSettings,
+ ActiveSettings localSettings,
+ ActiveStateHandler onActiveStateChanged) {
+ return new StreamHandler._(writer, incomingQueue, outgoingQueue,
+ peerSettings, localSettings, onActiveStateChanged, 1, 0);
+ }
+
+ factory StreamHandler.server(
+ FrameWriter writer,
+ ConnectionMessageQueueIn incomingQueue,
+ ConnectionMessageQueueOut outgoingQueue,
+ ActiveSettings peerSettings,
+ ActiveSettings localSettings,
+ ActiveStateHandler onActiveStateChanged) {
+ return new StreamHandler._(writer, incomingQueue, outgoingQueue,
+ peerSettings, localSettings, onActiveStateChanged, 2, -1);
+ }
+
+ void onTerminated(exception) {
+ _openStreams.values.toList().forEach((stream) =>
+ _closeStreamAbnormally(stream, exception, propagateException: true));
+ startClosing();
+ }
+
+ void forceDispatchIncomingMessages() {
+ _openStreams.forEach((int streamId, Http2StreamImpl stream) {
+ stream.incomingQueue.forceDispatchIncomingMessages();
+ });
+ }
+
+ Stream<TransportStream> get incomingStreams => _newStreamsC.stream;
+
+ List<TransportStream> get openStreams => _openStreams.values.toList();
+
+ void processInitialWindowSizeSettingChange(int difference) {
+ // If the initialFlowWindow size was changed via a SettingsFrame, all
+ // existing streams must be updated to reflect this change.
+ _openStreams.values.forEach((Http2StreamImpl stream) {
+ stream.windowHandler.processInitialWindowSizeSettingChange(difference);
+ });
+ }
+
+ void processGoawayFrame(GoawayFrame frame) {
+ var lastStreamId = frame.lastStreamId;
+ var streamIds = _openStreams.keys
+ .where((id) => id > lastStreamId && !_isPeerInitiatedStream(id))
+ .toList();
+ for (int id in streamIds) {
+ var exception = new StreamException(
+ id,
+ 'Remote end was telling us to stop. This stream was not processed '
+ 'and can therefore be retried (on a new connection).');
+ _closeStreamIdAbnormally(id, exception, propagateException: true);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //// New local/remote Stream handling
+ ////////////////////////////////////////////////////////////////////////////
+
+ bool _isPeerInitiatedStream(int streamId) {
+ bool isServerStreamId = streamId.isEven;
+ bool isLocalStream = isServerStreamId == isServer;
+ return !isLocalStream;
+ }
+
+ Http2StreamImpl newStream(List<Header> headers, {bool endStream: false}) {
+ return ensureNotTerminatedSync(() {
+ var stream = newLocalStream();
+ _sendHeaders(stream, headers, endStream: endStream);
+ return stream;
+ });
+ }
+
+ Http2StreamImpl newLocalStream() {
+ return ensureNotTerminatedSync(() {
+ assert(_canCreateNewStream());
+
+ if (MAX_STREAM_ID < nextStreamId) {
+ throw new StateError(
+ 'Cannot create new streams, since a wrap around would happen.');
+ }
+ int streamId = nextStreamId;
+ nextStreamId += 2;
+ return _newStreamInternal(streamId);
+ });
+ }
+
+ Http2StreamImpl newRemoteStream(int remoteStreamId) {
+ return ensureNotTerminatedSync(() {
+ assert(remoteStreamId <= MAX_STREAM_ID);
+ // NOTE: We cannot enforce that a new stream id is 2 higher than the last
+ // used stream id. Meaning there can be "holes" in the sense that stream
+ // ids are not used:
+ //
+ // http/2 spec:
+ // The first use of a new stream identifier implicitly closes all
+ // streams in the "idle" state that might have been initiated by that
+ // peer with a lower-valued stream identifier. For example, if a client
+ // sends a HEADERS frame on stream 7 without ever sending a frame on
+ // stream 5, then stream 5 transitions to the "closed" state when the
+ // first frame for stream 7 is sent or received.
+
+ if (remoteStreamId <= lastRemoteStreamId) {
+ throw new ProtocolException('Remote tried to open new stream which is '
+ 'not in "idle" state.');
+ }
+
+ bool sameDirection = (nextStreamId + remoteStreamId) % 2 == 0;
+ assert(!sameDirection);
+
+ lastRemoteStreamId = remoteStreamId;
+ return _newStreamInternal(remoteStreamId);
+ });
+ }
+
+ Http2StreamImpl _newStreamInternal(int streamId) {
+ // For each new stream we must:
+ // - setup sending/receiving [Window]s with correct initial size
+ // - setup sending/receiving WindowHandlers which take care of
+ // updating the windows.
+ // - setup incoming/outgoing stream queues, which buffer data
+ // that is not handled by
+ // * the application [incoming]
+ // * the underlying transport [outgoing]
+ // - register incoming stream queue in connection-level queue
+
+ var outgoingStreamWindow =
+ new Window(initialSize: _peerSettings.initialWindowSize);
+
+ var incomingStreamWindow =
+ new Window(initialSize: _localSettings.initialWindowSize);
+
+ var windowOutHandler =
+ new OutgoingStreamWindowHandler(outgoingStreamWindow);
+
+ var windowInHandler = new IncomingWindowHandler.stream(
+ _frameWriter, incomingStreamWindow, streamId);
+
+ var streamQueueIn = new StreamMessageQueueIn(windowInHandler);
+ var streamQueueOut =
+ new StreamMessageQueueOut(streamId, windowOutHandler, outgoingQueue);
+
+ incomingQueue.insertNewStreamMessageQueue(streamId, streamQueueIn);
+
+ var _outgoingC = new StreamController<StreamMessage>();
+ var stream = new Http2StreamImpl(
+ streamQueueIn,
+ streamQueueOut,
+ _outgoingC,
+ streamId,
+ windowOutHandler,
+ this._canPush,
+ this._push,
+ this._terminateStream);
+ final wasIdle = _openStreams.isEmpty;
+ _openStreams[stream.id] = stream;
+
+ _setupOutgoingMessageHandling(stream);
+
+ // Handle incoming stream cancellation. RST is only sent when streamQueueOut
+ // has been closed because RST make the stream 'closed'.
+ streamQueueIn.onCancel.then((_) {
+ // If our side is done sending data, i.e. we have enqueued the
+ // end-of-stream in the outgoing message queue, but the remote end is
+ // still sending us data, despite us not being interested in it, we will
+ // reset the stream.
+ if (stream.state == StreamState.HalfClosedLocal) {
+ stream.outgoingQueue.enqueueMessage(
+ new ResetStreamMessage(stream.id, ErrorCode.CANCEL));
+ }
+ });
+
+ // NOTE: We are not interested whether the streams were normally finished
+ // or abnormally terminated. Therefore we use 'catchError((_) {})'!
+ var streamDone = [streamQueueIn.done, streamQueueOut.done];
+ Future.wait(streamDone).catchError((_) {}).whenComplete(() {
+ _cleanupClosedStream(stream);
+ });
+
+ if (wasIdle) {
+ _onActiveStateChanged(true);
+ }
+
+ return stream;
+ }
+
+ bool _canPush(Http2StreamImpl stream) {
+ bool openState = (stream.state == StreamState.Open ||
+ stream.state == StreamState.HalfClosedRemote);
+ bool pushEnabled = this._peerSettings.enablePush;
+ return openState &&
+ pushEnabled &&
+ _canCreateNewStream() &&
+ !_ranOutOfStreamIds();
+ }
+
+ ServerTransportStream _push(
+ Http2StreamImpl stream, List<Header> requestHeaders) {
+ if (stream.state != StreamState.Open &&
+ stream.state != StreamState.HalfClosedRemote) {
+ throw new StateError('Cannot push based on a stream that is neither open '
+ 'nor half-closed-remote.');
+ }
+
+ if (!_peerSettings.enablePush) {
+ throw new StateError('Client did disable server pushes.');
+ }
+
+ if (!_canCreateNewStream()) {
+ throw new StateError('Maximum number of streams reached.');
+ }
+
+ if (_ranOutOfStreamIds()) {
+ throw new StateError('There are no more stream ids left. Please use a '
+ 'new connection.');
+ }
+
+ Http2StreamImpl pushStream = newLocalStream();
+
+ // NOTE: Since there was no real request from the client, we simulate it
+ // by adding a synthetic `endStream = true` Data message into the incoming
+ // queue.
+ _changeState(pushStream, StreamState.ReservedLocal);
+ // TODO: We should wait for us to send the headers frame before doing this
+ // transition.
+ _changeState(pushStream, StreamState.HalfClosedRemote);
+ pushStream.incomingQueue
+ .enqueueMessage(new DataMessage(stream.id, const <int>[], true));
+
+ _frameWriter.writePushPromiseFrame(
+ stream.id, pushStream.id, requestHeaders);
+
+ return pushStream;
+ }
+
+ void _terminateStream(Http2StreamImpl stream) {
+ if (stream.state == StreamState.Open ||
+ stream.state == StreamState.HalfClosedLocal ||
+ stream.state == StreamState.HalfClosedRemote ||
+ stream.state == StreamState.ReservedLocal ||
+ stream.state == StreamState.ReservedRemote) {
+ _frameWriter.writeRstStreamFrame(stream.id, ErrorCode.CANCEL);
+ _closeStreamAbnormally(stream, null, propagateException: false);
+ }
+ }
+
+ void _setupOutgoingMessageHandling(Http2StreamImpl stream) {
+ stream._outgoingCSubscription =
+ stream._outgoingC.stream.listen((StreamMessage msg) {
+ if (!wasTerminated) {
+ _handleNewOutgoingMessage(stream, msg);
+ }
+ }, onError: (error, stack) {
+ if (!wasTerminated) {
+ stream.terminate();
+ }
+ }, onDone: () {
+ if (!wasTerminated) {
+ // Stream should already have been closed by the last frame, but we
+ // allow multiple close calls, just to make sure.
+ _handleOutgoingClose(stream);
+ }
+ });
+ stream.outgoingQueue.bufferIndicator.bufferEmptyEvents.listen((_) {
+ if (stream._outgoingCSubscription.isPaused) {
+ stream._outgoingCSubscription.resume();
+ }
+ });
+ }
+
+ void _handleNewOutgoingMessage(Http2StreamImpl stream, StreamMessage msg) {
+ if (stream.state == StreamState.Idle) {
+ if (msg is! HeadersStreamMessage) {
+ var exception = new TransportException(
+ 'The first message on a stream needs to be a headers frame.');
+ _closeStreamAbnormally(stream, exception);
+ return;
+ }
+ _changeState(stream, StreamState.Open);
+ }
+
+ if (msg is DataStreamMessage) {
+ _sendData(stream, msg.bytes, endStream: msg.endStream);
+ } else if (msg is HeadersStreamMessage) {
+ _sendHeaders(stream, msg.headers, endStream: msg.endStream);
+ }
+
+ if (stream.outgoingQueue.bufferIndicator.wouldBuffer &&
+ !stream._outgoingCSubscription.isPaused) {
+ stream._outgoingCSubscription.pause();
+ }
+ }
+
+ void _handleOutgoingClose(Http2StreamImpl stream) {
+ // We allow multiple close calls.
+ if (stream.state != StreamState.HalfClosedLocal &&
+ stream.state != StreamState.Closed &&
+ stream.state != StreamState.Terminated) {
+ _sendData(stream, const [], endStream: true);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //// Process incoming stream frames
+ ////////////////////////////////////////////////////////////////////////////
+
+ void processStreamFrame(ConnectionState connectionState, Frame frame) {
+ try {
+ _processStreamFrameInternal(connectionState, frame);
+ } on StreamClosedException catch (exception) {
+ _frameWriter.writeRstStreamFrame(
+ exception.streamId, ErrorCode.STREAM_CLOSED);
+ _closeStreamIdAbnormally(exception.streamId, exception);
+ } on StreamException catch (exception) {
+ _frameWriter.writeRstStreamFrame(
+ exception.streamId, ErrorCode.INTERNAL_ERROR);
+ _closeStreamIdAbnormally(exception.streamId, exception);
+ }
+ }
+
+ void _processStreamFrameInternal(
+ ConnectionState connectionState, Frame frame) {
+ // If we initiated a close of the connection and the received frame belongs
+ // to a stream id which is higher than the last peer-initiated stream we
+ // processed, we'll ignore it.
+ // http/2 spec:
+ // After sending a GOAWAY frame, the sender can discard frames for
+ // streams initiated by the receiver with identifiers higher than the
+ // identified last stream. However, any frames that alter connection
+ // state cannot be completely ignored. For instance, HEADERS,
+ // PUSH_PROMISE, and CONTINUATION frames MUST be minimally processed to
+ // ensure the state maintained for header compression is consistent
+ // (see Section 4.3); similarly, DATA frames MUST be counted toward
+ // the connection flow-control window. Failure to process these
+ // frames can cause flow control or header compression state to become
+ // unsynchronized.
+ if (connectionState.activeFinishing &&
+ _isPeerInitiatedStream(frame.header.streamId) &&
+ frame.header.streamId > highestPeerInitiatedStream) {
+ // Even if the frame will be ignored, we still need to process it in a
+ // minimal way to ensure the connection window will be updated.
+ if (frame is DataFrame) {
+ incomingQueue.processIgnoredDataFrame(frame);
+ }
+ return null;
+ }
+
+ // TODO: Consider splitting this method into client/server handling.
+ return ensureNotTerminatedSync(() {
+ var stream = _openStreams[frame.header.streamId];
+ if (stream == null) {
+ bool frameBelongsToIdleStream() {
+ int streamId = frame.header.streamId;
+ bool isServerStreamId = frame.header.streamId.isEven;
+ bool isLocalStream = isServerStreamId == isServer;
+ bool isIdleStream = isLocalStream
+ ? streamId >= nextStreamId
+ : streamId > lastRemoteStreamId;
+ return isIdleStream;
+ }
+
+ if (_isPeerInitiatedStream(frame.header.streamId)) {
+ // Update highest stream id we received and processed (we update it
+ // before processing, so if it was an error, the client will not
+ // retry it).
+ _highestStreamIdReceived =
+ max(_highestStreamIdReceived, frame.header.streamId);
+ }
+
+ if (frame is HeadersFrame) {
+ if (isServer) {
+ Http2StreamImpl newStream = newRemoteStream(frame.header.streamId);
+ _changeState(newStream, StreamState.Open);
+
+ _handleHeadersFrame(newStream, frame);
+ _newStreamsC.add(newStream);
+ } else {
+ // A server cannot open new streams to the client. The only way
+ // for a server to start a new stream is via a PUSH_PROMISE_FRAME.
+ throw new ProtocolException(
+ 'HTTP/2 clients cannot receive HEADER_FRAMEs as a connection'
+ 'attempt.');
+ }
+ } else if (frame is WindowUpdateFrame) {
+ if (frameBelongsToIdleStream()) {
+ // We treat this as a protocol error even though not enforced
+ // or specified by the HTTP/2 spec.
+ throw new ProtocolException(
+ 'Got a WINDOW_UPDATE_FRAME for an "idle" stream id.');
+ } else {
+ // We must be able to receive window update frames for streams that
+ // have been already closed. The specification does not mention
+ // what happens if the streamId is belonging to an "idle" / unused
+ // stream.
+ }
+ } else if (frame is RstStreamFrame) {
+ if (frameBelongsToIdleStream()) {
+ // [RstFrame]s for streams which haven't been established (known as
+ // idle streams) must be treated as a connection error.
+ throw new ProtocolException(
+ 'Got a RST_STREAM_FRAME for an "idle" stream id.');
+ } else {
+ // [RstFrame]s for already dead (known as "closed") streams should
+ // be ignored. (If the stream was in "HalfClosedRemote" and we did
+ // send an endStream=true, it will be removed from the stream set).
+ }
+ } else if (frame is PriorityFrame) {
+ // http/2 spec:
+ // The PRIORITY frame can be sent for a stream in the "idle" or
+ // "closed" states. This allows for the reprioritization of a
+ // group of dependent streams by altering the priority of an
+ // unused or closed parent stream.
+ //
+ // As long as we do not handle stream priorities, we can safely ignore
+ // such frames on idle streams.
+ //
+ // NOTE: Firefox for example sends [PriorityFrame]s even without
+ // opening any streams (e.g. streams 3,5,7,9,11 [PriorityFrame]s and
+ // stream 13 is the first real stream opened by a [HeadersFrame].
+ //
+ // TODO: When implementing priorities for HTTP/2 streams, these frames
+ // need to be taken into account.
+ } else if (frame is PushPromiseFrame) {
+ throw new ProtocolException('Cannot push on a non-existent stream '
+ '(stream ${frame.header.streamId} does not exist)');
+ } else {
+ throw new StreamClosedException(
+ frame.header.streamId,
+ 'No open stream found and was not a headers frame opening a '
+ 'new stream.');
+ }
+ } else {
+ if (frame is HeadersFrame) {
+ _handleHeadersFrame(stream, frame);
+ } else if (frame is DataFrame) {
+ _handleDataFrame(stream, frame);
+ } else if (frame is PushPromiseFrame) {
+ _handlePushPromiseFrame(stream, frame);
+ } else if (frame is WindowUpdateFrame) {
+ _handleWindowUpdate(stream, frame);
+ } else if (frame is RstStreamFrame) {
+ _handleRstFrame(stream, frame);
+ } else {
+ throw new ProtocolException(
+ 'Unsupported frame type ${frame.runtimeType}.');
+ }
+ }
+ });
+ }
+
+ void _handleHeadersFrame(Http2StreamImpl stream, HeadersFrame frame) {
+ if (stream.state == StreamState.ReservedRemote) {
+ _changeState(stream, StreamState.HalfClosedLocal);
+ }
+
+ if (stream.state != StreamState.Open &&
+ stream.state != StreamState.HalfClosedLocal) {
+ throw new StreamClosedException(
+ stream.id, 'Expected open state (was: ${stream.state}).');
+ }
+
+ incomingQueue.processHeadersFrame(frame);
+
+ if (frame.hasEndStreamFlag) _handleEndOfStreamRemote(stream);
+ }
+
+ void _handleDataFrame(Http2StreamImpl stream, DataFrame frame) {
+ if (stream.state != StreamState.Open &&
+ stream.state != StreamState.HalfClosedLocal) {
+ throw new StreamClosedException(
+ stream.id, 'Expected open state (was: ${stream.state}).');
+ }
+
+ incomingQueue.processDataFrame(frame);
+
+ if (frame.hasEndStreamFlag) _handleEndOfStreamRemote(stream);
+ }
+
+ void _handlePushPromiseFrame(Http2StreamImpl stream, PushPromiseFrame frame) {
+ if (stream.state != StreamState.Open &&
+ stream.state != StreamState.HalfClosedLocal) {
+ throw new ProtocolException(
+ 'Expected open state (was: ${stream.state}).');
+ }
+
+ var pushedStream = newRemoteStream(frame.promisedStreamId);
+ _changeState(pushedStream, StreamState.ReservedRemote);
+
+ incomingQueue.processPushPromiseFrame(frame, pushedStream);
+ }
+
+ void _handleWindowUpdate(Http2StreamImpl stream, WindowUpdateFrame frame) {
+ stream.windowHandler.processWindowUpdate(frame);
+ }
+
+ void _handleRstFrame(Http2StreamImpl stream, RstStreamFrame frame) {
+ stream._handleTerminated(frame.errorCode);
+ var exception = new StreamTransportException(
+ 'Stream was terminated by peer (errorCode: ${frame.errorCode}).');
+ _closeStreamAbnormally(stream, exception, propagateException: true);
+ }
+
+ void _handleEndOfStreamRemote(Http2StreamImpl stream) {
+ if (stream.state == StreamState.Open) {
+ _changeState(stream, StreamState.HalfClosedRemote);
+ } else if (stream.state == StreamState.HalfClosedLocal) {
+ _changeState(stream, StreamState.Closed);
+ // TODO: We have to make sure that we
+ // - remove the stream for data structures which only care about the
+ // state
+ // - keep the stream in data structures which need to be emptied
+ // (e.g. MessageQueues which are not empty yet).
+ _openStreams.remove(stream.id);
+ } else {
+ throw new StateError(
+ 'Got an end-of-stream from the remote end, but this stream is '
+ 'neither in the Open nor in the HalfClosedLocal state. '
+ 'This should never happen.');
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //// Process outgoing stream messages
+ ////////////////////////////////////////////////////////////////////////////
+
+ void _sendHeaders(Http2StreamImpl stream, List<Header> headers,
+ {bool endStream: false}) {
+ if (stream.state != StreamState.Idle &&
+ stream.state != StreamState.Open &&
+ stream.state != StreamState.HalfClosedRemote) {
+ throw new StateError('Idle state expected.');
+ }
+
+ stream.outgoingQueue
+ .enqueueMessage(new HeadersMessage(stream.id, headers, endStream));
+
+ if (stream.state == StreamState.Idle) {
+ _changeState(stream, StreamState.Open);
+ }
+
+ if (endStream) {
+ _endStream(stream);
+ }
+ }
+
+ void _sendData(Http2StreamImpl stream, List<int> data,
+ {bool endStream: false}) {
+ if (stream.state != StreamState.Open &&
+ stream.state != StreamState.HalfClosedRemote) {
+ throw new StateError('Open state expected (was: ${stream.state}).');
+ }
+
+ stream.outgoingQueue
+ .enqueueMessage(new DataMessage(stream.id, data, endStream));
+
+ if (endStream) {
+ _endStream(stream);
+ }
+ }
+
+ void _endStream(Http2StreamImpl stream) {
+ if (stream.state == StreamState.Open) {
+ _changeState(stream, StreamState.HalfClosedLocal);
+ } else if (stream.state == StreamState.HalfClosedRemote) {
+ _changeState(stream, StreamState.Closed);
+ } else {
+ throw new StateError(
+ 'Invalid state transition. This should never happen.');
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //// Stream closing
+ ////////////////////////////////////////////////////////////////////////////
+
+ void _cleanupClosedStream(Http2StreamImpl stream) {
+ // NOTE: This function should only be called once
+ // * all incoming data has been delivered to the application
+ // * all outgoing data has been added to the connection queue.
+ incomingQueue.removeStreamMessageQueue(stream.id);
+ _openStreams.remove(stream.id);
+ if (stream.state != StreamState.Terminated) {
+ _changeState(stream, StreamState.Terminated);
+ }
+ if (_openStreams.isEmpty) {
+ _onActiveStateChanged(false);
+ }
+ onCheckForClose();
+ }
+
+ void _closeStreamIdAbnormally(int streamId, Exception exception,
+ {bool propagateException: false}) {
+ Http2StreamImpl stream = _openStreams[streamId];
+ if (stream != null) {
+ _closeStreamAbnormally(stream, exception,
+ propagateException: propagateException);
+ }
+ }
+
+ void _closeStreamAbnormally(Http2StreamImpl stream, Object exception,
+ {bool propagateException: false}) {
+ incomingQueue.removeStreamMessageQueue(stream.id);
+
+ if (stream.state != StreamState.Terminated) {
+ _changeState(stream, StreamState.Terminated);
+ }
+ stream.incomingQueue.terminate(propagateException ? exception : null);
+ stream._outgoingCSubscription.cancel();
+ stream._outgoingC.close();
+
+ // NOTE: we're not adding an error here.
+ stream.outgoingQueue.terminate();
+
+ onCheckForClose();
+ }
+
+ void onClosing() {
+ _newStreamsC.close();
+ }
+
+ void onCheckForClose() {
+ if (isClosing && _openStreams.isEmpty) {
+ closeWithValue();
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ //// State transitioning & Counting of active streams
+ ////////////////////////////////////////////////////////////////////////////
+
+ /// The number of streams which we initiated and which are in one of the open
+ /// states (i.e. [StreamState.Open], [StreamState.HalfClosedLocal] or
+ /// [StreamState.HalfClosedRemote])
+ int _numberOfActiveStreams = 0;
+
+ bool _canCreateNewStream() {
+ int limit = _peerSettings.maxConcurrentStreams;
+ return limit == null || _numberOfActiveStreams < limit;
+ }
+
+ bool _ranOutOfStreamIds() {
+ return nextStreamId > MAX_STREAM_ID;
+ }
+
+ void _changeState(Http2StreamImpl stream, StreamState to) {
+ StreamState from = stream.state;
+
+ // In checked mode we'll test that the state transition is allowed.
+ assert((from == StreamState.Idle && to == StreamState.ReservedLocal) ||
+ (from == StreamState.Idle && to == StreamState.ReservedRemote) ||
+ (from == StreamState.Idle && to == StreamState.Open) ||
+ (from == StreamState.Open && to == StreamState.HalfClosedLocal) ||
+ (from == StreamState.Open && to == StreamState.HalfClosedRemote) ||
+ (from == StreamState.Open && to == StreamState.Closed) ||
+ (from == StreamState.HalfClosedLocal && to == StreamState.Closed) ||
+ (from == StreamState.HalfClosedRemote && to == StreamState.Closed) ||
+ (from == StreamState.ReservedLocal &&
+ to == StreamState.HalfClosedRemote) ||
+ (from == StreamState.ReservedLocal && to == StreamState.Closed) ||
+ (from == StreamState.ReservedRemote && to == StreamState.Closed) ||
+ (from == StreamState.ReservedRemote &&
+ to == StreamState.HalfClosedLocal) ||
+ (from != StreamState.Terminated && to == StreamState.Terminated));
+
+ // If we initiated the stream and it became "open" or "closed" we need to
+ // update the [_numberOfActiveStreams] counter.
+ if (_didInitiateStream(stream)) {
+ // NOTE: We wait until the stream is completely done.
+ // (If we waited only until `StreamState.Closed` then we might still have
+ // the endStream header/data message buffered, but not yet sent out).
+ switch (stream.state) {
+ case StreamState.ReservedLocal:
+ case StreamState.ReservedRemote:
+ case StreamState.Idle:
+ if (to == StreamState.Open ||
+ to == StreamState.HalfClosedLocal ||
+ to == StreamState.HalfClosedRemote) {
+ _numberOfActiveStreams++;
+ }
+ break;
+ case StreamState.Open:
+ case StreamState.HalfClosedLocal:
+ case StreamState.HalfClosedRemote:
+ case StreamState.Closed:
+ if (to == StreamState.Terminated) {
+ _numberOfActiveStreams--;
+ }
+ break;
+ case StreamState.Terminated:
+ // There is nothing to do here.
+ break;
+ }
+ }
+ stream.state = to;
+ }
+
+ bool _didInitiateStream(Http2StreamImpl stream) {
+ int id = stream.id;
+ return (isServer && id.isEven) || (!isServer && id.isOdd);
+ }
+}
diff --git a/http2/lib/src/sync_errors.dart b/http2/lib/src/sync_errors.dart
new file mode 100644
index 0000000..f8afa05
--- /dev/null
+++ b/http2/lib/src/sync_errors.dart
@@ -0,0 +1,49 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library http2.src.sync_errors;
+
+class ProtocolException implements Exception {
+ final String _message;
+
+ ProtocolException(this._message);
+
+ String toString() => 'ProtocolError: $_message';
+}
+
+class FlowControlException implements Exception {
+ final String _message;
+
+ FlowControlException(this._message);
+
+ String toString() => 'FlowControlException: $_message';
+}
+
+class FrameSizeException implements Exception {
+ final String _message;
+
+ FrameSizeException(this._message);
+
+ String toString() => 'FrameSizeException: $_message';
+}
+
+class TerminatedException implements Exception {
+ String toString() => 'TerminatedException: The object has been terminated.';
+}
+
+class StreamException implements Exception {
+ final String _message;
+ final int streamId;
+
+ StreamException(this.streamId, this._message);
+
+ String toString() => 'StreamException(stream id: $streamId): $_message';
+}
+
+class StreamClosedException extends StreamException {
+ StreamClosedException(int streamId, [String message = ''])
+ : super(streamId, message);
+
+ String toString() => 'StreamClosedException(stream id: $streamId): $_message';
+}
diff --git a/http2/lib/src/testing/client.dart b/http2/lib/src/testing/client.dart
new file mode 100644
index 0000000..a7d9dc4
--- /dev/null
+++ b/http2/lib/src/testing/client.dart
@@ -0,0 +1,152 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library http2.client;
+
+import 'dart:async';
+import 'dart:convert' show ascii;
+import 'dart:io';
+
+import '../../transport.dart';
+
+class Request {
+ final String method;
+ final Uri uri;
+
+ Request(this.method, this.uri);
+}
+
+class Response {
+ final Map<String, List<String>> headers;
+ final Stream<List<int>> stream;
+ final Stream<ServerPush> serverPushes;
+
+ Response(this.headers, this.stream, this.serverPushes);
+}
+
+class ServerPush {
+ final Map<String, List<String>> requestHeaders;
+ final Future<Response> response;
+
+ ServerPush(this.requestHeaders, this.response);
+}
+
+class ClientConnection {
+ final ClientTransportConnection connection;
+
+ /// Assumes the protocol on [socket] was negogiated to be http/2.
+ ///
+ /// If [settings] are omitted, the default [ClientSettings] will be used.
+ ClientConnection(Socket socket, {ClientSettings settings})
+ : connection =
+ new ClientTransportConnection.viaSocket(socket, settings: settings);
+
+ Future<Response> makeRequest(Request request) {
+ var path = request.uri.path;
+ if (path.isEmpty) path = '/';
+
+ var headers = [
+ new Header.ascii(':method', request.method),
+ new Header.ascii(':path', path),
+ new Header.ascii(':scheme', request.uri.scheme),
+ new Header.ascii(':authority', '${request.uri.host}'),
+ ];
+
+ return _handleStream(connection.makeRequest(headers, endStream: true));
+ }
+
+ Future close() {
+ return connection.finish();
+ }
+
+ Future<Response> _handleStream(ClientTransportStream stream) {
+ var completer = new Completer<Response>();
+ bool isFirst = true;
+ var controller = new StreamController<List<int>>();
+ var serverPushController = new StreamController<ServerPush>(sync: true);
+ stream.incomingMessages.listen((StreamMessage msg) {
+ if (isFirst) {
+ isFirst = false;
+ var headerMap = _convertHeaders((msg as HeadersStreamMessage).headers);
+ completer.complete(new Response(
+ headerMap, controller.stream, serverPushController.stream));
+ } else {
+ controller.add((msg as DataStreamMessage).bytes);
+ }
+ }, onDone: controller.close);
+ _handlePeerPushes(stream.peerPushes).pipe(serverPushController);
+ return completer.future;
+ }
+
+ Stream<ServerPush> _handlePeerPushes(
+ Stream<TransportStreamPush> serverPushes) {
+ var pushesController = new StreamController<ServerPush>();
+ serverPushes.listen((TransportStreamPush push) {
+ var responseCompleter = new Completer<Response>();
+ var serverPush = new ServerPush(
+ _convertHeaders(push.requestHeaders), responseCompleter.future);
+
+ pushesController.add(serverPush);
+
+ bool isFirst = true;
+ var dataController = new StreamController<List<int>>();
+ push.stream.incomingMessages.listen((StreamMessage msg) {
+ if (isFirst) {
+ isFirst = false;
+ var headerMap =
+ _convertHeaders((msg as HeadersStreamMessage).headers);
+ var response = new Response(
+ headerMap, dataController.stream, new Stream.fromIterable([]));
+ responseCompleter.complete(response);
+ } else {
+ dataController.add((msg as DataStreamMessage).bytes);
+ }
+ }, onDone: dataController.close);
+ }, onDone: pushesController.close);
+ return pushesController.stream;
+ }
+
+ Map<String, List<String>> _convertHeaders(List<Header> headers) {
+ var headerMap = <String, List<String>>{};
+ for (var header in headers) {
+ headerMap
+ .putIfAbsent(ascii.decode(header.name), () => [])
+ .add(ascii.decode(header.value));
+ }
+ return headerMap;
+ }
+}
+
+/// Tries to connect to [uri] via a secure socket connection and establishes a
+/// http/2 connection.
+///
+/// If [allowServerPushes] is `true`, server pushes need to be handled by the
+/// client. The maximum number of concurrent server pushes can be configured via
+/// [maxConcurrentPushes] (default is `null` meaning no limit).
+Future<ClientConnection> connect(Uri uri,
+ {bool allowServerPushes: false, int maxConcurrentPushes}) async {
+ const List<String> Http2AlpnProtocols = const <String>[
+ 'h2-14',
+ 'h2-15',
+ 'h2-16',
+ 'h2-17',
+ 'h2'
+ ];
+
+ bool useSSL = uri.scheme == 'https';
+ var settings = new ClientSettings(
+ concurrentStreamLimit: maxConcurrentPushes,
+ allowServerPushes: allowServerPushes);
+ if (useSSL) {
+ SecureSocket socket = await SecureSocket.connect(uri.host, uri.port,
+ supportedProtocols: Http2AlpnProtocols);
+ if (!Http2AlpnProtocols.contains(socket.selectedProtocol)) {
+ throw new Exception('Server does not support HTTP/2.');
+ }
+ return new ClientConnection(socket, settings: settings);
+ } else {
+ Socket socket = await Socket.connect(uri.host, uri.port);
+ return new ClientConnection(socket, settings: settings);
+ }
+}
diff --git a/http2/lib/src/testing/debug.dart b/http2/lib/src/testing/debug.dart
new file mode 100644
index 0000000..b4e0b2c
--- /dev/null
+++ b/http2/lib/src/testing/debug.dart
@@ -0,0 +1,139 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library http2.debug;
+
+import 'dart:async';
+import 'dart:convert';
+import 'dart:io';
+
+import '../../transport.dart';
+import '../connection_preface.dart';
+import '../frames/frames.dart';
+import '../settings/settings.dart';
+
+final jsonEncoder = new JsonEncoder.withIndent(' ');
+
+TransportConnection debugPrintingConnection(Socket socket,
+ {bool isServer: true, bool verbose: true}) {
+ TransportConnection connection;
+
+ var incoming = decodeVerbose(socket, isServer, verbose: verbose);
+ var outgoing = decodeOutgoingVerbose(socket, isServer, verbose: verbose);
+ if (isServer) {
+ connection = new ServerTransportConnection.viaStreams(incoming, outgoing);
+ } else {
+ connection = new ClientTransportConnection.viaStreams(incoming, outgoing);
+ }
+ return connection;
+}
+
+Stream<List<int>> decodeVerbose(Stream<List<int>> inc, bool isServer,
+ {bool verbose: true}) {
+ String name = isServer ? 'server' : 'client';
+
+ var sc = new StreamController<List<int>>();
+ var sDebug = new StreamController<List<int>>();
+
+ _pipeAndCopy(inc, sc, sDebug);
+
+ if (!isServer) {
+ _decodeFrames(sDebug.stream).listen((frame) {
+ print('[$name/stream:${frame.header.streamId}] '
+ 'Incoming ${frame.runtimeType}:');
+ if (verbose) {
+ print(jsonEncoder.convert(frame.toJson()));
+ print('');
+ }
+ }, onError: (e, s) {
+ print('[$name] Stream error: $e.');
+ }, onDone: () {
+ print('[$name] Closed.');
+ });
+ } else {
+ var s3 = readConnectionPreface(sDebug.stream);
+ _decodeFrames(s3).listen((frame) {
+ print('[$name/stream:${frame.header.streamId}] '
+ 'Incoming ${frame.runtimeType}:');
+ if (verbose) {
+ print(jsonEncoder.convert(frame.toJson()));
+ print('');
+ }
+ }, onError: (e, s) {
+ print('[$name] Stream error: $e.');
+ }, onDone: () {
+ print('[$name] Closed.');
+ });
+ }
+
+ return sc.stream;
+}
+
+StreamSink<List<int>> decodeOutgoingVerbose(
+ StreamSink<List<int>> sink, bool isServer,
+ {bool verbose: true}) {
+ String name = isServer ? 'server' : 'client';
+
+ var proxySink = new StreamController<List<int>>();
+ var copy = new StreamController<List<int>>();
+
+ if (!isServer) {
+ _decodeFrames(readConnectionPreface(copy.stream)).listen((Frame frame) {
+ print('[$name/stream:${frame.header.streamId}] '
+ 'Outgoing ${frame.runtimeType}:');
+ if (verbose) {
+ print(jsonEncoder.convert(frame.toJson()));
+ print('');
+ }
+ }, onError: (e, s) {
+ print('[$name] Outgoing stream error: $e');
+ }, onDone: () {
+ print('[$name] Closing.');
+ });
+ } else {
+ _decodeFrames(copy.stream).listen((Frame frame) {
+ print('[$name/stream:${frame.header.streamId}] '
+ 'Outgoing ${frame.runtimeType}:');
+ if (verbose) {
+ print(jsonEncoder.convert(frame.toJson()));
+ print('');
+ }
+ }, onError: (e, s) {
+ print('[$name] Outgoing stream error: $e');
+ }, onDone: () {
+ print('[$name] Closing.');
+ proxySink.close();
+ });
+ }
+
+ _pipeAndCopy(proxySink.stream, sink, copy);
+
+ return proxySink;
+}
+
+Stream<Frame> _decodeFrames(Stream<List<int>> bytes) {
+ var settings = new ActiveSettings();
+ var decoder = new FrameReader(bytes, settings);
+ return decoder.startDecoding();
+}
+
+Future _pipeAndCopy(Stream<List<int>> from, StreamSink to, StreamSink to2) {
+ var c = new Completer();
+ from.listen((List<int> data) {
+ to.add(data);
+ to2.add(data);
+ }, onError: (e, StackTrace s) {
+ to.addError(e, s);
+ to2.addError(e, s);
+ }, onDone: () {
+ Future.wait([to.close(), to2.close()])
+ .then(c.complete)
+ .catchError(c.completeError);
+ });
+ return c.future;
+}
+
+void print(String s) {
+ stderr.writeln(s);
+}
diff --git a/http2/lib/transport.dart b/http2/lib/transport.dart
new file mode 100644
index 0000000..c0ec7bd
--- /dev/null
+++ b/http2/lib/transport.dart
@@ -0,0 +1,236 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:io';
+
+import 'src/connection.dart';
+import 'src/hpack/hpack.dart' show Header;
+
+export 'src/hpack/hpack.dart' show Header;
+
+typedef void ActiveStateHandler(bool isActive);
+
+/// Settings for a [TransportConnection].
+abstract class Settings {
+ /// The maximum number of concurrent streams the remote end can open
+ /// (defaults to being unlimited).
+ final int concurrentStreamLimit;
+
+ /// The default stream window size the remote peer can use when creating new
+ /// streams (defaults to 65535 bytes).
+ final int streamWindowSize;
+
+ const Settings({this.concurrentStreamLimit, this.streamWindowSize});
+}
+
+/// Settings for a [TransportConnection] a server can make.
+class ServerSettings extends Settings {
+ const ServerSettings({int concurrentStreamLimit, int streamWindowSize})
+ : super(
+ concurrentStreamLimit: concurrentStreamLimit,
+ streamWindowSize: streamWindowSize);
+}
+
+/// Settings for a [TransportConnection] a client can make.
+class ClientSettings extends Settings {
+ /// Whether the client allows pushes from the server (defaults to false).
+ final bool allowServerPushes;
+
+ const ClientSettings(
+ {int concurrentStreamLimit,
+ int streamWindowSize,
+ this.allowServerPushes: false})
+ : super(
+ concurrentStreamLimit: concurrentStreamLimit,
+ streamWindowSize: streamWindowSize);
+}
+
+/// Represents a HTTP/2 connection.
+abstract class TransportConnection {
+ /// Pings the other end.
+ Future ping();
+
+ /// Sets the active state callback.
+ ///
+ /// This callback is invoked with `true` when the number of active streams
+ /// goes from 0 to 1 (the connection goes from idle to active), and with
+ /// `false` when the number of active streams becomes 0 (the connection goes
+ /// from active to idle).
+ set onActiveStateChanged(ActiveStateHandler callback);
+
+ /// Finish this connection.
+ ///
+ /// No new streams will be accepted or can be created.
+ Future finish();
+
+ /// Terminates this connection forcefully.
+ Future terminate();
+}
+
+abstract class ClientTransportConnection extends TransportConnection {
+ factory ClientTransportConnection.viaSocket(Socket socket,
+ {ClientSettings settings}) =>
+ new ClientTransportConnection.viaStreams(socket, socket,
+ settings: settings);
+
+ factory ClientTransportConnection.viaStreams(
+ Stream<List<int>> incoming, StreamSink<List<int>> outgoing,
+ {ClientSettings settings}) {
+ if (settings == null) settings = const ClientSettings();
+ return new ClientConnection(incoming, outgoing, settings);
+ }
+
+ /// Whether this connection is open and can be used to make new requests
+ /// via [makeRequest].
+ bool get isOpen;
+
+ /// Creates a new outgoing stream.
+ ClientTransportStream makeRequest(List<Header> headers,
+ {bool endStream: false});
+}
+
+abstract class ServerTransportConnection extends TransportConnection {
+ factory ServerTransportConnection.viaSocket(Socket socket,
+ {ServerSettings settings}) {
+ return new ServerTransportConnection.viaStreams(socket, socket,
+ settings: settings);
+ }
+
+ factory ServerTransportConnection.viaStreams(
+ Stream<List<int>> incoming, StreamSink<List<int>> outgoing,
+ {ServerSettings settings:
+ const ServerSettings(concurrentStreamLimit: 1000)}) {
+ if (settings == null) settings = const ServerSettings();
+ return new ServerConnection(incoming, outgoing, settings);
+ }
+
+ /// Incoming HTTP/2 streams.
+ Stream<ServerTransportStream> get incomingStreams;
+}
+
+/// Represents a HTTP/2 stream.
+abstract class TransportStream {
+ /// The id of this stream.
+ ///
+ /// * odd numbered streams are client streams
+ /// * even numbered streams are opened from the server
+ int get id;
+
+ /// A stream of data and/or headers from the remote end.
+ Stream<StreamMessage> get incomingMessages;
+
+ /// A sink for writing data and/or headers to the remote end.
+ StreamSink<StreamMessage> get outgoingMessages;
+
+ /// Sets the termination handler on this stream.
+ ///
+ /// The handler will be called if the stream receives an RST_STREAM frame.
+ set onTerminated(void value(int v));
+
+ /// Terminates this HTTP/2 stream in an un-normal way.
+ ///
+ /// For normal termination, one can cancel the [StreamSubscription] from
+ /// `incoming.listen()` and close the `outgoing` [StreamSink].
+ ///
+ /// Terminating this HTTP/2 stream will free up all resources associated with
+ /// it locally and will notify the remote end that this stream is no longer
+ /// used.
+ void terminate();
+
+ // For convenience only.
+ void sendHeaders(List<Header> headers, {bool endStream: false}) {
+ outgoingMessages
+ .add(new HeadersStreamMessage(headers, endStream: endStream));
+ if (endStream) outgoingMessages.close();
+ }
+
+ void sendData(List<int> bytes, {bool endStream: false}) {
+ outgoingMessages.add(new DataStreamMessage(bytes, endStream: endStream));
+ if (endStream) outgoingMessages.close();
+ }
+}
+
+abstract class ClientTransportStream extends TransportStream {
+ /// Streams which the remote end pushed to this endpoint.
+ ///
+ /// If peer pushes were enabled, the client is responsible to either
+ /// handle or reject any peer push.
+ Stream<TransportStreamPush> get peerPushes;
+}
+
+abstract class ServerTransportStream extends TransportStream {
+ /// Whether a method to [push] will succeed. Requirements for this getter to
+ /// return `true` are:
+ /// * this stream must be in the Open or HalfClosedRemote state
+ /// * the client needs to have the "enable push" settings enabled
+ /// * the number of active streams has not reached the maximum
+ bool get canPush;
+
+ /// Pushes a new stream to the remote peer.
+ ServerTransportStream push(List<Header> requestHeaders);
+}
+
+/// Represents a message which can be sent over a HTTP/2 stream.
+abstract class StreamMessage {
+ final bool endStream;
+
+ StreamMessage({bool endStream}) : this.endStream = endStream ?? false;
+}
+
+/// Represents a data message which can be sent over a HTTP/2 stream.
+class DataStreamMessage extends StreamMessage {
+ final List<int> bytes;
+
+ DataStreamMessage(this.bytes, {bool endStream}) : super(endStream: endStream);
+
+ String toString() => 'DataStreamMessage(${bytes.length} bytes)';
+}
+
+/// Represents a headers message which can be sent over a HTTP/2 stream.
+class HeadersStreamMessage extends StreamMessage {
+ final List<Header> headers;
+
+ HeadersStreamMessage(this.headers, {bool endStream})
+ : super(endStream: endStream);
+
+ String toString() => 'HeadersStreamMessage(${headers.length} headers)';
+}
+
+/// Represents a remote stream push.
+class TransportStreamPush {
+ /// The request headers which [stream] is the response to.
+ final List<Header> requestHeaders;
+
+ /// The remote stream push.
+ final ClientTransportStream stream;
+
+ TransportStreamPush(this.requestHeaders, this.stream);
+
+ String toString() =>
+ 'TransportStreamPush(${requestHeaders.length} request headers headers)';
+}
+
+/// An exception thrown by the HTTP/2 implementation.
+class TransportException implements Exception {
+ final String message;
+
+ TransportException(this.message);
+
+ String toString() => 'HTTP/2 error: $message';
+}
+
+/// An exception thrown when a HTTP/2 connection error occurred.
+class TransportConnectionException extends TransportException {
+ final int errorCode;
+
+ TransportConnectionException(int errorCode, String details)
+ : errorCode = errorCode,
+ super('Connection error: $details (errorCode: $errorCode)');
+}
+
+/// An exception thrown when a HTTP/2 stream error occured.
+class StreamTransportException extends TransportException {
+ StreamTransportException(String details) : super('Stream error: $details');
+}
diff --git a/http2/manual_test/out_of_stream_ids_test.dart b/http2/manual_test/out_of_stream_ids_test.dart
new file mode 100644
index 0000000..4e22f4b
--- /dev/null
+++ b/http2/manual_test/out_of_stream_ids_test.dart
@@ -0,0 +1,61 @@
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE FILE.
+
+/// ---------------------------------------------------------------------------
+/// In order to run this test one needs to change the following line in
+/// ../lib/src/streams/stream_handler.dart
+///
+/// - static const int MAX_STREAM_ID = (1 << 31) - 1;
+/// + static const int MAX_STREAM_ID = (1 << 5) - 1;
+///
+/// without this patch this test will run for a _long_ time.
+/// ---------------------------------------------------------------------------
+
+import 'dart:async';
+
+import 'package:test/test.dart';
+import 'package:http2/transport.dart';
+import 'package:http2/src/streams/stream_handler.dart';
+
+import '../test/transport_test.dart';
+
+main() {
+ group('transport-test', () {
+ transportTest('client-runs-out-of-stream-ids',
+ (ClientTransportConnection client,
+ ServerTransportConnection server) async {
+ Future serverFun() async {
+ await for (ServerTransportStream stream in server.incomingStreams) {
+ stream.sendHeaders([new Header.ascii('x', 'y')], endStream: true);
+ expect(await stream.incomingMessages.toList(), hasLength(1));
+ }
+ await server.finish();
+ }
+
+ Future clientFun() async {
+ var headers = [new Header.ascii('a', 'b')];
+
+ const kMaxStreamId = StreamHandler.MAX_STREAM_ID;
+ for (int i = 1; i <= kMaxStreamId; i += 2) {
+ var stream = client.makeRequest(headers, endStream: true);
+ var messages = await stream.incomingMessages.toList();
+ expect(messages, hasLength(1));
+ }
+
+ expect(client.isOpen, false);
+ expect(() => client.makeRequest(headers),
+ throwsA(const TypeMatcher<StateError>()));
+
+ await new Future.delayed(const Duration(seconds: 1));
+ await client.finish();
+ }
+
+ var serverFuture = serverFun();
+ var clientFuture = clientFun();
+
+ await serverFuture;
+ await clientFuture;
+ });
+ });
+}
diff --git a/http2/pubspec.yaml b/http2/pubspec.yaml
new file mode 100644
index 0000000..e3e2651
--- /dev/null
+++ b/http2/pubspec.yaml
@@ -0,0 +1,12 @@
+name: http2
+version: 1.0.0
+description: A HTTP/2 implementation in Dart.
+author: Dart Team <misc@dartlang.org>
+homepage: https://github.com/dart-lang/http2
+
+environment:
+ sdk: '>=2.0.0-dev.56.0 <3.0.0'
+
+dev_dependencies:
+ mockito: ^4.0.0
+ test: ^1.2.0